具有自引用外键的Alembic SQLite ALTER TABLE

Ern*_*ons 6 sqlite database-migration foreign-key-relationship alembic

SQLite数据库的Alembic迁移:

def upgrade():
    with op.batch_alter_table('my_table') as batch_op:
        batch_op.add_column(sa.Column('parent_id', sa.String(24)))
        batch_op.create_foreign_key('parent_constraint', 'my_table', ['parent_id'], ['id'])
Run Code Online (Sandbox Code Playgroud)

应该创建对同一个表的外键parent_id引用,创建对名为的表的引用:idmy_table_alembic_batch_temp

CREATE TABLE "my_table" (
    id VARCHAR(24) NOT NULL, 
    parent_id VARCHAR(24), 
    PRIMARY KEY (id), 
    CONSTRAINT parent_constraint FOREIGN KEY(parent_id) REFERENCES _alembic_batch_temp (id)
)
Run Code Online (Sandbox Code Playgroud)

如何在更改表时创建自引用约束?

小智 5

经过一些研究,我发现这里的问题是Alembic进行批量迁移的方式.简而言之,在Alembic的当前版本(0.7.6)中,不可能通过迁移创建与self的关系.

  1. 如Alembic 文档中所述,要进行迁移,将使用临时名称创建新表,并从alter table代码更改.在这种情况下:

    CREATE TABLE _alembic_batch_temp (
        id VARCHAR(24) NOT NULL, 
        parent_id VARCHAR(24), 
        PRIMARY KEY (id), 
        CONSTRAINT parent_constraint FOREIGN KEY(parent_id) REFERENCES _alembic_batch_temp (id)
    )
    
    Run Code Online (Sandbox Code Playgroud)
  2. 该表填充了旧表中的数据:

    INSERT INTO _alembic_batch_temp (id) SELECT id FROM my_table;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 然后删除旧表:

    DROP TABLE my_table;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 最后,新创建的表被重命名为它的正确名称:

    ALTER TABLE _alembic_batch_temp RENAME TO my_table;
    
    Run Code Online (Sandbox Code Playgroud)

在第一个代码片段中已经可以看到这种做事方式的问题.新创建的外键引用临时表,一旦创建它,​​由于SQLite中的限制,它无法更改.因此,在重新命名表后,您最终会得到您提供的表格:

CREATE TABLE "my_table" (  # new name
    id VARCHAR(24) NOT NULL, 
    parent_id VARCHAR(24), 
    PRIMARY KEY (id), 
    CONSTRAINT parent_constraint FOREIGN KEY(parent_id) REFERENCES _alembic_batch_temp (id)  # old reference
)
Run Code Online (Sandbox Code Playgroud)

要避免这种情况,您可以手动创建批量迁移:

  1. 将旧表重命名为一些临时名称:

    ALTER TABLE my_table RENAME TO migration_temp_table;
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用正确的名称和适当的参考创建新表:

    CREATE TABLE my_table (
        id VARCHAR(24) NOT NULL, 
        parent_id VARCHAR(24), 
        PRIMARY KEY (id), 
        CONSTRAINT parent_constraint FOREIGN KEY(parent_id) REFERENCES my_table (id)
    )
    
    Run Code Online (Sandbox Code Playgroud)
  3. 复制数据:

    INSERT INTO my_table (id) SELECT id FROM migration_temp_table;
    
    Run Code Online (Sandbox Code Playgroud)
  4. 删除旧表:

    DROP TABLE migration_temp_table;
    
    Run Code Online (Sandbox Code Playgroud)

  • 显然,至少从SQLite 3.13.0(.Net 1.0.102.0)不再需要您的解决方法.我用这个版本测试了这个确切的场景(第一个),它按预期工作:自引用用表重命名.我甚至尝试使用ON DELETE CASCADE,我的数据保持原样:) (2认同)