如何在SQLAlchemy中删除外键约束?

13 投票
4 回答
16211 浏览
提问于 2025-04-15 20:19

我正在使用 SQLAlchemy Migrate 来跟踪数据库的变化,但在删除外键时遇到了问题。我有两个表,一个是 t_new(新表),另一个是 t_exists(已有表)。我需要先添加 t_new,然后在 t_exists 中添加一个外键。接着,我还需要能够撤销这个操作(这就是我遇到麻烦的地方)。

t_new = sa.Table("new", meta.metadata,
    sa.Column("new_id", sa.types.Integer, primary_key=True)
)
t_exists = sa.Table("exists", meta.metadata,
    sa.Column("exists_id", sa.types.Integer, primary_key=True),
    sa.Column(
        "new_id", 
        sa.types.Integer,
        sa.ForeignKey("new.new_id", onupdate="CASCADE", ondelete="CASCADE"),
        nullable=False
    )
)

这样做是没问题的:

t_new.create()
t_exists.c.new_id.create()

但是这样做就不行:

t_exists.c.new_id.drop()
t_new.drop()

尝试删除外键列时出现了错误:1025,“在重命名 '.\my_db_name\#sql-1b0_2e6' 到 '.\my_db_name\exists' 时出错(errno: 150)”。

如果我使用原始 SQL,我可以手动删除外键,然后再删除列,但我还没弄明白如何用 SQLAlchemy 删除外键?我该如何删除外键,然后再删除列呢?

4 个回答

0

我觉得你可以用SQLAlchemy-Migrate来实现这个功能。需要注意的是,外键(ForeignKey)是针对单独的列设置的,而外键约束(ForeignKeyConstraint)是在表的层面上,把多个列关联起来。如果你查看某一列上的外键对象,你会发现它实际上是引用了一个外键约束。

我无法测试这个想法,因为我用的两个数据库中,MS SQL不支持SqlAlchemy-Migrate,而sqlite不支持对约束进行“修改表”(alter table)。不过,我确实让SQLAlchemy尝试通过删除sqlite表上的引用约束来移除一个外键,所以看起来是可行的。具体情况可能会有所不同。

3

我通过创建一个单独的元数据实例,并使用 Session.execute() 来运行原始 SQL 语句,成功实现了这个功能。理想情况下,我希望能有一个完全使用 sqlalchemy 的解决方案,这样我就不需要依赖 MySQL 特定的解决方案。不过目前为止,我还不知道有这样的解决方案。

8

你可以使用sqlalchemy.migrate来实现这个功能。

为了让它正常工作,我必须明确地创建外键约束,而不是像之前那样隐式地使用Column('fk', ForeignKey('fk_table.field')):

可惜的是,不要这样做:

p2 = Table('tablename',
            metadata,
            Column('id', Integer, primary_key=True),
            Column('fk', ForeignKey('fk_table.field')),
            mysql_engine='InnoDB',
           )

要这样做:

p2 = Table('tablename',
            metadata,
            Column('id', Integer, primary_key=True),
            Column('fk', Integer, index=True),
            mysql_engine='InnoDB',
            )
ForeignKeyConstraint(columns=[p2.c.fk], refcolumns=[p3.c.id]).create()

然后删除的过程看起来是这样的:

def downgrade(migrate_engine):
     # First drop the constraint
     ForeignKeyConstraint(columns=[p2.c.fk], refcolumns=[p3.c.id]).drop()
     # Then drop the table
     p2.drop()

撰写回答