30

The autoincrement argument in SQLAlchemy seems to be only True and False, but I want to set the pre-defined value aid = 1001, the via autoincrement aid = 1002 when the next insert is done.

In SQL, can be changed like:

ALTER TABLE article AUTO_INCREMENT = 1001;

I'm using MySQL and I have tried following, but it doesn't work:

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Article(Base):
    __tablename__ = 'article'
    aid = Column(INTEGER(unsigned=True, zerofill=True), 
                autoincrement=1001, primary_key=True)

So, how can I get that? Thanks in advance!

Gorthon
  • 303
  • 1
  • 3
  • 5

6 Answers6

24

You can achieve this by using DDLEvents. This will allow you to run additional SQL statements just after the CREATE TABLE ran. Look at the examples in the link, but I am guessing your code will look similar to below:

from sqlalchemy import event
from sqlalchemy import DDL
event.listen(
    Article.__table__,
    "after_create",
    DDL("ALTER TABLE %(table)s AUTO_INCREMENT = 1001;")
)
van
  • 74,297
  • 13
  • 168
  • 171
  • 1
    Is there a way to exclude this from getting executed depending on the db provider? The ALTER ... AUTO_INCREMENT works fine for MySQL (and most other dbs I think), however, this SQL is apparently unsupported for SQLITE. The closest workaround I could find is to do an insert/delete: http://stackoverflow.com/questions/692856/set-start-value-for-autoincrement-in-sqlite – coderfi May 19 '14 at 20:00
  • 1
    Answered my own question. Poking through the code, I found the solution, which is documented by: http://docs.sqlalchemy.org/en/rel_0_8/core/ddl.html#sqlalchemy.schema.DDLElement.execute_if The above could be rewritten as event.listen( Article.__table__, "after_create", DDL("ALTER TABLE %(table)s AUTO_INCREMENT = 1001;").execute_if(dialect=('postgresql', 'mysql')) ) which gets me around the sqlite problem. – coderfi May 19 '14 at 20:08
20

According to the docs:

autoincrement – This flag may be set to False to indicate an integer primary key column that should not be considered to be the “autoincrement” column, that is the integer primary key column which generates values implicitly upon INSERT and whose value is usually returned via the DBAPI cursor.lastrowid attribute. It defaults to True to satisfy the common use case of a table with a single integer primary key column.

So, autoincrement is only a flag to let SQLAlchemy know whether it's the primary key you want to increment.

What you're trying to do is to create a custom autoincrement sequence.

So, your example, I think, should look something like:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Sequence

Base = declarative_base()

class Article(Base):
    __tablename__ = 'article'
    aid = Column(INTEGER(unsigned=True, zerofill=True), 
                 Sequence('article_aid_seq', start=1001, increment=1),   
                 primary_key=True)

Note, I don't know whether you're using PostgreSQL or not, so you should make note of the following if you are:

The Sequence object also implements special functionality to accommodate Postgresql’s SERIAL datatype. The SERIAL type in PG automatically generates a sequence that is used implicitly during inserts. This means that if a Table object defines a Sequence on its primary key column so that it works with Oracle and Firebird, the Sequence would get in the way of the “implicit” sequence that PG would normally use. For this use case, add the flag optional=True to the Sequence object - this indicates that the Sequence should only be used if the database provides no other option for generating primary key identifiers.

Edwardr
  • 2,906
  • 3
  • 27
  • 30
  • I'm using MySQL. According to the docs: It only has an effect on databases which have explicit support for sequences, which currently includes **Postgresql**, **Oracle**, and **Firebird**. I tried your code, but it still doesn't work. – Gorthon May 08 '12 at 10:46
  • Ahhh, my bad (I use PG whenever I can ya see). In that case, I'd take a peak at @van's answer above! – Edwardr May 08 '12 at 14:32
  • Sorry for my poor English.(I don't know the meaning of `my bad` and `take a peak at`) In fact, @van's answer is later than yours. Thanks very very much! – Gorthon May 09 '12 at 10:56
  • 1
    `my bad` - means "it was my fault, sorry". I meant to type `take a peek at`, but made a typo. It means "go and have a look at". – Edwardr May 09 '12 at 12:34
  • This is the true answer, DDL is dirty way to do that – pylover Apr 30 '14 at 12:57
  • It's weird that they require an `optional=True` flag to do something that should be the default behavior. Why _wouldn't_ someone want the behavior described above for PG w.r.t. Serial/Sequence? – code_dredd Feb 08 '19 at 00:26
3

I couldn't get the other answers to work using mysql and flask-migrate so I did the following inside a migration file.

from app import db
db.engine.execute("ALTER TABLE myDB.myTable AUTO_INCREMENT = 2000;")

Be warned that if you regenerated your migration files this will get overwritten.

cbron
  • 4,036
  • 3
  • 33
  • 40
3

I know this is an old question but I recently had to figure this out and none of the available answer were quite what I needed. The solution I found relied on Sequence in SQLAlchemy. For whatever reason, I could not get it to work when I called the Sequence constructor within the Column constructor as has been referenced above. As a note, I am using PostgreSQL.

For your answer I would have put it as such:

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Sequence, Column, Integer

import os
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Sequence, Integer, create_engine
Base = declarative_base()

def connection():
    engine = create_engine(f"postgresql://postgres:{os.getenv('PGPASSWORD')}@localhost:{os.getenv('PGPORT')}/test")
    return engine

engine = connection()

class Article(Base):
    __tablename__ = 'article'
    seq = Sequence('article_aid_seq', start=1001)
    aid = Column('aid', Integer, seq, server_default=seq.next_value(), primary_key=True)

Base.metadata.create_all(engine)

This then can be called in PostgreSQL with:

insert into article (aid) values (DEFAULT);
select * from article;

 aid  
------
 1001
(1 row)

Hope this helps someone as it took me a while

1

You can do it using the mysql_auto_increment table create option. There are mysql_engine and mysql_default_charset options too, which might be also handy:

article = Table(
    'article', metadata,
    Column('aid', INTEGER(unsigned=True, zerofill=True), primary_key=True),
    mysql_engine='InnoDB',
    mysql_default_charset='utf8',
    mysql_auto_increment='1001',
)

The above will generate:

CREATE TABLE article (
    aid INTEGER UNSIGNED ZEROFILL NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY (aid)
)ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
ericbn
  • 10,163
  • 3
  • 47
  • 55
1

If your database supports Identity columns*, the starting value can be set like this:

import sqlalchemy as sa

tbl = sa.Table(
    't10494033',
    sa.MetaData(),
    sa.Column('id', sa.Integer, sa.Identity(start=200, always=True), primary_key=True),
)

Resulting in this DDL output:

CREATE TABLE t10494033 (
        id INTEGER GENERATED ALWAYS AS IDENTITY (START WITH 200), 
        PRIMARY KEY (id)
)

Identity(..) is ignored if the backend does not support it.


* PostgreSQL 10+, Oracle 12+ and MSSQL, according to the linked documentation above.

snakecharmerb
  • 47,570
  • 11
  • 100
  • 153