我想添加一个自动增量列,该列不是现有 MySQL 数据库的主键。
此操作所需的在服务器上发出的命令如下:
ALTER TABLE `mytable` ADD `id` INT UNIQUE NOT NULL AUTO_INCREMENT FIRST
我面临的问题是如何通过 Alembic 迁移来复制此表更改。我试过了:
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('mytable', sa.Colummn('id', sa.INTEGER(),
nullable=False, autoincrement=True)
但是当我尝试使用以下命令插入行时:
INSERT INTO `mytable` (`col1`, `col2`) VALUES (`bar`);
其中
col1
、col2
是不可为 null 的列。当我插入这条记录时,我希望表能够自动为我生成 id。
ERROR 1364 (HY000): Field 'id' doesn't have a default value
如果我使用以下命令检查 Alembic 自动生成的 sql:
alembic upgrade 'hash-of-revision' --sql
对于给定的修订版,它会吐出:
ALTER TABLE mytable ADD COLUMN id INTEGER NOT NULL;
这意味着 Alembic 或 SQLAlchemy 在生成迁移的 sql 时忽略
autoincrement
字段。
有什么办法可以解决这个问题吗?或者我可以基于自定义sql命令建立迁移吗?
首先我们添加列并允许其具有空值
op.add_column('abc', Column('id', BIGINT(unsigned=True), comment='This column stores the type of phrase') )
然后我们创建主键
op.create_primary_key( 'abc_pk', table_name='abc', columns=['id'] )
不确定它如何允许我在空列上添加主键,但我想这是因为它位于事务块中。
然后我们将该列更改为自动增量列
op.alter_column( existing_type=BIGINT(unsigned=True), table_name='abc', column_name='id', autoincrement=True, existing_autoincrement=True, nullable=False)
从 SQLAlchemy 1.4+ 和 Alembic 1.9 开始,您可以使用 Identity 类型,根据 docs,它取代了 Serial 类型。
这个声明式 ORM:
class ProductOption(Base):
__tablename__:str = 'product_options'
id:Mapped[int] = mapped_column(Integer, server_default=Identity(start=1, cycle=True), primary_key=True)
uuid:Mapped[UUID] = mapped_column(UUID, nullable=False, unique=True)
name:Mapped[str] = mapped_column(String(50), nullable=False)
price:Mapped[Decimal] = mapped_column(Numeric(16, 4), nullable=False)
cost:Mapped[Decimal] = mapped_column(Numeric(16, 4), nullable=False)
unit:Mapped[str] = mapped_column(String(8), nullable=False)
duration:Mapped[int] = mapped_column(Integer)
导致以下 Alebic
--autogenerate
迁移:
op.create_table(
"product_options",
sa.Column(
"id",
sa.Integer(),
sa.Identity(always=False, start=1, cycle=True),
nullable=False,
),
sa.Column("uuid", sa.UUID(), nullable=False),
sa.Column("name", sa.String(length=50), nullable=False),
sa.Column("price", sa.Numeric(precision=16, scale=4), nullable=False),
sa.Column("cost", sa.Numeric(precision=16, scale=4), nullable=False),
sa.Column("unit", sa.String(length=8), nullable=False),
sa.Column("duration", sa.Integer(), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("uuid"),
)
不幸的是,如果表中有数据,@Nelson 的答案将不起作用
不确定它如何允许我在空列上添加主键,但我想这是因为它位于事务块中。
我相信这是因为如果数据库中没有行,则列中没有
NULL
值。
这是我使用 MySQL 8.0.34 的工作:
# add NOT NULL `id` column, for now is not autoincrement
op.add_column('table_name', sa.Column('id', mysql.INTEGER(unsigned=True), nullable=False, autoincrement=False))
# drop current primary key so that can create a new one
op.drop_constraint(None, 'table_name', 'primary')
# add new primary key with the same columns as the old one + the `id` column
op.create_primary_key(None, "table_name", ["id", "previous_pk_1", "previous_pk_2"])
# alter the `id` column to be autoincrement (can't make an autoincrement column that is not part of a primary key)
# this will assign autoincrement values to the column (before this it is all 0s)
op.alter_column('table_name', 'id',
existing_type=mysql.INTEGER(unsigned=True),
existing_nullable=False,
autoincrement=True)
# remove the autoincrement from the column to allow dropping the primary key
op.alter_column('table_name', 'id',
existing_type=mysql.INTEGER(unsigned=True),
existing_nullable=False,
autoincrement=False)
# drop the primary key
op.drop_constraint(None, 'table_name', 'primary')
# recreate the primary key, now with just the `id` column
op.create_primary_key(None, "table_name", ["id"])
# re-add autoincrement to the column
op.alter_column('table_name', 'id',
existing_type=mysql.INTEGER(unsigned=True),
existing_nullable=False,
autoincrement=True)