私のdbモデルにオブジェクトUser
が含まれているとします。
_Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(String(32), primary_key=True, default=...)
name = Column(Unicode(100))
_
私のデータベースには、n行のusers
テーブルが含まれています。ある時点で、私は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')
_
この問題には、多かれ少なかれハックな解決策があるようです。 これ と これ はどちらも使用を提案します execute()
と bulk_insert()
移行中に生のSQLステートメントを実行します。 この(不完全な)ソリューション 現在のdbモデルをインポートしますが、そのモデルが変更されると、そのアプローチは壊れやすくなります。
Alembicの移行中に列データの既存のコンテンツを移行および変更するにはどうすればよいですか?推奨される方法は何ですか、それはどこに文書化されていますか?
norbertpy’s answer で提案されたソリューションは最初はいいように聞こえますが、根本的な欠陥が1つあると思います。複数のトランザクションが導入され、ステップ間でDBがファンキーで一貫性のない状態になります。また、ツールがDBのデータなしでDBのスキーマを移行することも奇妙に思えます( 私のコメント を参照)。 2つは緊密に結合されているため、それらを分離できません。
いくつかの会話といくつかの会話の後( 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')
_
このソリューションに関する2つのコメント:
downgrade()
関数も同様に実装できます。
補遺。スキーマの移行とデータの移行のペアの例については、Alembicクックブックの 条件付き移行要素 セクションを参照してください。
alembicはスキーマ移行ツールであり、データ移行ではありません。そのように使用することもできますが。そのため、多くのドキュメントが見つかりません。とはいえ、3つの個別のリビジョンを作成することになります。
firstname
を削除せずにlastname
とname
を追加するアプリと同じようにすべてのユーザーを読み取り、名前を分割してから、first
とlast
を更新します。例えば.
for user in session.query(User).all():
user.firstname, user.lastname = user.name.split(' ')
session.commit()
削除name