在 Alembic 迁移期间更新列内容
Update column content during Alembic migration
假设我的数据库模型包含一个对象 User
:
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(String(32), primary_key=True, default=...)
name = Column(Unicode(100))
我的数据库包含一个 users
table 和 n 行。在某些时候,我决定将 name
拆分为 firstname
和 lastname
,并且在 alembic upgrade head
期间我希望我的数据也被迁移。
自动生成的Alembic迁移如下:
def upgrade():
op.add_column('users', sa.Column('lastname', sa.Unicode(length=50), nullable=True))
op.add_column('users', sa.Column('firstname', sa.Unicode(length=50), nullable=True))
# Assuming that the two new columns have been committed and exist at
# this point, I would like to iterate over all rows of the name column,
# split the string, write it into the new firstname and lastname rows,
# and once that has completed, continue to delete the name column.
op.drop_column('users', 'name')
def downgrade():
op.add_column('users', sa.Column('name', sa.Unicode(length=100), nullable=True))
# Do the reverse of the above.
op.drop_column('users', 'firstname')
op.drop_column('users', 'lastname')
这个问题似乎有多个或多或少的 hacky 解决方案。 This one and this one both propose to use execute()
and bulk_insert()
to execute raw SQL statements during a migration. This (incomplete) solution 导入当前的数据库模型,但当该模型发生变化时,该方法很脆弱。
如何在 Alembic 迁移期间迁移和修改列数据的现有内容?推荐的方法是什么,记录在哪里?
alembic 是模式迁移工具,而不是数据迁移工具。虽然它也可以那样使用。这就是为什么你不会找到很多关于它的文档的原因。也就是说,我会创建三个单独的修订版:
- 添加
firstname
和 lastname
而不删除 name
像在您的应用中一样读取所有用户并拆分他们的姓名,然后更新 first
和 last
。例如
for user in session.query(User).all():
user.firstname, user.lastname = user.name.split(' ')
session.commit()
移除name
sounds good at first, but I think it has one fundamental flaw: it would introduce multiple transactions—in between the steps, the DB would be in a funky, inconsistent state. It also seems odd to me (see ) 中提出的解决方案,该工具将在没有数据库数据的情况下迁移数据库的模式;两者联系得太紧密,无法分开。
经过一番摸索和多次交谈(参见 this Gist 中的代码片段),我决定采用以下解决方案:
def upgrade():
# Schema migration: add all the new columns.
op.add_column('users', sa.Column('lastname', sa.Unicode(length=50), nullable=True))
op.add_column('users', sa.Column('firstname', sa.Unicode(length=50), nullable=True))
# Data migration: takes a few steps...
# Declare ORM table views. Note that the view contains old and new columns!
t_users = sa.Table(
'users',
sa.MetaData(),
sa.Column('id', sa.String(32)),
sa.Column('name', sa.Unicode(length=100)), # Old column.
sa.Column('lastname', sa.Unicode(length=50)), # Two new columns.
sa.Column('firstname', sa.Unicode(length=50)),
)
# Use Alchemy's connection and transaction to noodle over the data.
connection = op.get_bind()
# Select all existing names that need migrating.
results = connection.execute(sa.select([
t_users.c.id,
t_users.c.name,
])).fetchall()
# Iterate over all selected data tuples.
for id_, name in results:
# Split the existing name into first and last.
firstname, lastname = name.rsplit(' ', 1)
# Update the new columns.
connection.execute(t_users.update().where(t_users.c.id == id_).values(
lastname=lastname,
firstname=firstname,
))
# Schema migration: drop the old column.
op.drop_column('users', 'name')
关于此解决方案的两条评论:
- 如所引用的 Gist 中所述,较新版本的 Alembic 的表示法略有不同。
- 根据数据库驱动程序,代码的行为可能不同。显然,MySQL 而不是 将上述代码作为单个事务处理(参见 “Statements That Cause an Implicit Commit”)。所以你必须检查你的数据库实现。
downgrade()
函数可以类似实现。
附录。有关架构迁移与数据迁移配对的示例,请参阅 Alembic 说明书中的 Conditional Migration Elements 部分。
假设我的数据库模型包含一个对象 User
:
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(String(32), primary_key=True, default=...)
name = Column(Unicode(100))
我的数据库包含一个 users
table 和 n 行。在某些时候,我决定将 name
拆分为 firstname
和 lastname
,并且在 alembic upgrade head
期间我希望我的数据也被迁移。
自动生成的Alembic迁移如下:
def upgrade():
op.add_column('users', sa.Column('lastname', sa.Unicode(length=50), nullable=True))
op.add_column('users', sa.Column('firstname', sa.Unicode(length=50), nullable=True))
# Assuming that the two new columns have been committed and exist at
# this point, I would like to iterate over all rows of the name column,
# split the string, write it into the new firstname and lastname rows,
# and once that has completed, continue to delete the name column.
op.drop_column('users', 'name')
def downgrade():
op.add_column('users', sa.Column('name', sa.Unicode(length=100), nullable=True))
# Do the reverse of the above.
op.drop_column('users', 'firstname')
op.drop_column('users', 'lastname')
这个问题似乎有多个或多或少的 hacky 解决方案。 This one and this one both propose to use execute()
and bulk_insert()
to execute raw SQL statements during a migration. This (incomplete) solution 导入当前的数据库模型,但当该模型发生变化时,该方法很脆弱。
如何在 Alembic 迁移期间迁移和修改列数据的现有内容?推荐的方法是什么,记录在哪里?
alembic 是模式迁移工具,而不是数据迁移工具。虽然它也可以那样使用。这就是为什么你不会找到很多关于它的文档的原因。也就是说,我会创建三个单独的修订版:
- 添加
firstname
和lastname
而不删除name
像在您的应用中一样读取所有用户并拆分他们的姓名,然后更新
first
和last
。例如for user in session.query(User).all(): user.firstname, user.lastname = user.name.split(' ') session.commit()
移除
name
经过一番摸索和多次交谈(参见 this Gist 中的代码片段),我决定采用以下解决方案:
def upgrade():
# Schema migration: add all the new columns.
op.add_column('users', sa.Column('lastname', sa.Unicode(length=50), nullable=True))
op.add_column('users', sa.Column('firstname', sa.Unicode(length=50), nullable=True))
# Data migration: takes a few steps...
# Declare ORM table views. Note that the view contains old and new columns!
t_users = sa.Table(
'users',
sa.MetaData(),
sa.Column('id', sa.String(32)),
sa.Column('name', sa.Unicode(length=100)), # Old column.
sa.Column('lastname', sa.Unicode(length=50)), # Two new columns.
sa.Column('firstname', sa.Unicode(length=50)),
)
# Use Alchemy's connection and transaction to noodle over the data.
connection = op.get_bind()
# Select all existing names that need migrating.
results = connection.execute(sa.select([
t_users.c.id,
t_users.c.name,
])).fetchall()
# Iterate over all selected data tuples.
for id_, name in results:
# Split the existing name into first and last.
firstname, lastname = name.rsplit(' ', 1)
# Update the new columns.
connection.execute(t_users.update().where(t_users.c.id == id_).values(
lastname=lastname,
firstname=firstname,
))
# Schema migration: drop the old column.
op.drop_column('users', 'name')
关于此解决方案的两条评论:
- 如所引用的 Gist 中所述,较新版本的 Alembic 的表示法略有不同。
- 根据数据库驱动程序,代码的行为可能不同。显然,MySQL 而不是 将上述代码作为单个事务处理(参见 “Statements That Cause an Implicit Commit”)。所以你必须检查你的数据库实现。
downgrade()
函数可以类似实现。
附录。有关架构迁移与数据迁移配对的示例,请参阅 Alembic 说明书中的 Conditional Migration Elements 部分。