Alembic のアップグレード中にデータを変更する必要があります。
現在、最初のリビジョンに「プレイヤー」テーブルがあります:
def upgrade():
op.create_table('player',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.Unicode(length=200), nullable=False),
sa.Column('position', sa.Unicode(length=200), nullable=True),
sa.Column('team', sa.Unicode(length=100), nullable=True)
sa.PrimaryKeyConstraint('id')
)
「チーム」テーブルを導入したいと思います。2 番目のリビジョンを作成しました:
def upgrade():
op.create_table('teams',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=False)
)
op.add_column('players', sa.Column('team_id', sa.Integer(), nullable=False))
2 回目の移行では次のデータも追加したいと思います。
チームテーブルに入力します:
INSERT INTO teams (name) SELECT DISTINCT team FROM players;
players.team 名に基づいて players.team_id を更新します。
UPDATE players AS p JOIN teams AS t SET p.team_id = t.id WHERE p.team = t.name;
アップグレード スクリプト内で挿入と更新を実行するにはどうすればよいですか?
ベストアンサー1
あなたが求めているのはデータ移行、とは対照的にスキーマ移行これは、Alembic ドキュメントで最も一般的です。
この回答では、モデルを定義するために宣言型 (class-Mapper-Table や core ではなく) を使用していることを前提としています。これを他の形式に適応させるのは比較的簡単なはずです。
Alembic はいくつかの基本的なデータ関数を提供していることに注意してください。op.bulk_insert()
そしてop.execute()
操作が最小限であれば、それらを使用します。移行に関係やその他の複雑な相互作用が必要な場合は、以下で説明するように、モデルとセッションの機能をフルに活用することをお勧めします。
以下は、セッションでデータを操作するために使用されるいくつかの宣言型モデルを設定する移行スクリプトの例です。重要なポイントは次のとおりです。
必要な列を含む、必要な基本モデルを定義します。すべての列が必要なわけではなく、主キーと使用する列だけが必要です。
アップグレード機能内では、
op.get_bind()
現在の接続を取得し、それを使用してセッションを作成します。- または、
bind.execute()
SQLAlchemy の下位レベルを使用して SQL クエリを直接記述します。これは単純な移行に便利です。
- または、
アプリケーションで通常使用するのと同じようにモデルとセッションを使用します。
"""create teams table
Revision ID: 169ad57156f0
Revises: 29b4c2bfce6d
Create Date: 2014-06-25 09:00:06.784170
"""
revision = '169ad57156f0'
down_revision = '29b4c2bfce6d'
from alembic import op
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Player(Base):
__tablename__ = 'players'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, nullable=False)
team_name = sa.Column('team', sa.String, nullable=False)
team_id = sa.Column(sa.Integer, sa.ForeignKey('teams.id'), nullable=False)
team = orm.relationship('Team', backref='players')
class Team(Base):
__tablename__ = 'teams'
id = sa.Column(sa.Integer, primary_key=True)
name = sa.Column(sa.String, nullable=False, unique=True)
def upgrade():
bind = op.get_bind()
session = orm.Session(bind=bind)
# create the teams table and the players.team_id column
Team.__table__.create(bind)
op.add_column('players', sa.Column('team_id', sa.ForeignKey('teams.id'), nullable=False)
# create teams for each team name
teams = {name: Team(name=name) for name in session.query(Player.team).distinct()}
session.add_all(teams.values())
# set player team based on team name
for player in session.query(Player):
player.team = teams[player.team_name]
session.commit()
# don't need team name now that team relationship is set
op.drop_column('players', 'team')
def downgrade():
bind = op.get_bind()
session = orm.Session(bind=bind)
# re-add the players.team column
op.add_column('players', sa.Column('team', sa.String, nullable=False)
# set players.team based on team relationship
for player in session.query(Player):
player.team_name = player.team.name
session.commit()
op.drop_column('players', 'team_id')
op.drop_table('teams')
移行では、コード内のモデルが表すモデルが別々に定義されるため、現在の状態データベースの移行は途中のステップデータベースはそのパス上の任意の状態にある可能性があるため、モデルはまだデータベースと同期していない可能性があります。非常に注意しないと、実際のモデルを直接使用すると、列の欠落、無効なデータなどの問題が発生します。移行で使用する列とモデルを正確に明示的に指定すると明確になります。