1. ホーム
  2. python

[解決済み] Alembicアップグレードスクリプトでインサートやアップデートを実行するにはどうすればよいですか?

2022-07-29 22:07:36

質問

Alembicのアップグレード時にデータを変更する必要があります。

私は現在、最初のリビジョンで 'players' テーブルを持っています。

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')
    )

teams' テーブルを導入したい。 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回目のマイグレーションでは、以下のデータも追加してほしいです。

  1. チーム テーブルにデータを入力します。

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
    
    
  2. players.team名に基づきplayers.team_idを更新します。

    UPDATE players AS p JOIN teams AS t SET p.team_id = t.id WHERE p.team = t.name;
    
    

アップグレードスクリプト内で挿入や更新を実行するにはどうすればよいですか?

どのように解決するのですか?

あなたが求めているのは データの移行 とは対照的に スキーママイグレーション というのは、Alembic のドキュメントで最も一般的なものです。

この回答は、モデルを定義するためにdeclarative(class-Mapper-Tableやcoreとは対照的)を使用していることを前提としています。 これを他の形式に適応させるのは比較的簡単なはずです。

Alembicはいくつかの基本的なデータ関数を提供していることに注意してください。 op.bulk_insert() op.execute() . もし操作がかなり最小限のものであれば、それらを使用します。 移行にリレーションシップやその他の複雑な相互作用が必要な場合は、以下で説明するように、モデルとセッションのフルパワーを使用することをお勧めします。

以下は、セッションでデータを操作するために使用されるいくつかの宣言的なモデルを設定するマイグレーションスクリプトの例です。 キーポイントは

  1. 必要なカラムを含む、基本的なモデルを定義します。 すべてのカラムが必要なわけではなく、主キーと使用するカラムだけでよい。

  2. アップグレード機能内では op.get_bind() を使用して現在の接続を取得し、その接続でセッションを作成します。

    • または bind.execute() を使えば、SQLAlchemy の下位レベルを使って、SQL クエリを直接書けます。これは単純なマイグレーションに便利です。
  3. アプリケーションで通常行うように、モデルとセッションを使用します。

"""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')

マイグレーションで別々のモデルを定義しているのは、コード中のモデルが 現在の状態 を表し、マイグレーションは 途中のステップ . データベースはその途中のどの状態であってもよいので、モデルはまだデータベースと同期していないかもしれません。よほど注意しない限り、実際のモデルを直接使用すると、カラムがない、データが無効であるなどの問題が発生します。マイグレーションで使用するカラムとモデルを正確に明示する方が明確です。