web-dev-qa-db-ja.com

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

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回目の移行で次のデータも追加したいと思います。

  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;
    

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

69
Arek S

あなたが求めているのは、データ移行であり、スキーマ移行これは、Alembicドキュメントで最も一般的です。

この答えは、(class-Mapper-Tableまたはコアではなく)宣言を使用してモデルを定義していることを前提としています。これを他の形式に適応させるのは比較的簡単です。

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):
    __table= '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):
    __table= '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')

コード内のモデルはデータベースの現在の状態を表し、移行はを表すため、移行は個別のモデルを定義します道に沿ったステップ。データベースは、そのパスに沿った状態にある可能性があるため、モデルはまだデータベースと同期していない可能性があります。細心の注意を払わない限り、実際のモデルを直接使用すると、列の欠落、無効なデータなどの問題が発生します。移行で使用する列とモデルを正確に明示する方が明確です。

116
davidism

アドホックテーブルを使用してSqlAlchemyコアステートメントを使用することをお勧めします 公式ドキュメントに詳細が記載されているように 。 SqlAlchemy Coreは、移行スクリプトの両方の長所です。

概念の例を次に示します。

from sqlalchemy.sql import table, column
from sqlalchemy import String
from alembic import op

account = table('account',
    column('name', String)
)
op.execute(
    account.update().\\
    where(account.c.name==op.inline_literal('account 1')).\\
        values({'name':op.inline_literal('account 2')})
        )

# If insert is required
from sqlalchemy.sql import insert
from sqlalchemy import orm

session = orm.Session(bind=bind)
bind = op.get_bind()

data = {
    "name": "John",
}
ret = session.execute(insert(account).values(data))
# for use in other insert calls
account_id = ret.lastrowid
2
cmc

次の例のように、直接SQLを使用することもできます( Alembic Operation Reference )。

from alembic import op

# revision identifiers, used by Alembic.
revision = '1ce7873ac4ced2'
down_revision = '1cea0ac4ced2'
branch_labels = None
depends_on = None


def upgrade():
    # ### commands made by andrew ###
    op.execute('UPDATE STOCK SET IN_STOCK = -1 WHERE IN_STOCK IS NULL')
    # ### end Alembic commands ###


def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    pass
    # ### end Alembic commands ###
1
Martlark