Bagaimana cara saya mengeksekusi penyisipan dan pembaruan dalam skrip peningkatan Alembic?

102

Saya perlu mengubah data selama peningkatan Alembic.

Saat ini saya memiliki tabel 'pemain' dalam revisi pertama:

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

Saya ingin memperkenalkan tabel 'tim'. Saya telah membuat revisi kedua:

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

Saya ingin migrasi kedua juga menambahkan data berikut:

  1. Mengisi tabel tim:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
    
  2. Perbarui pemain.team_id berdasarkan nama pemain.team:

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

Bagaimana cara saya mengeksekusi penyisipan dan pembaruan di dalam skrip pemutakhiran?

Arek S
sumber

Jawaban:

158

Apa yang Anda minta adalah migrasi data , bukan migrasi skema yang paling umum di dokumen Alembic.

Jawaban ini mengasumsikan Anda menggunakan deklaratif (sebagai lawan dari class-Mapper-Table atau core) untuk mendefinisikan model Anda. Harus relatif mudah untuk menyesuaikannya dengan bentuk lain.

Perhatikan bahwa Alembic menyediakan beberapa fungsi data dasar: op.bulk_insert()dan op.execute(). Jika operasinya cukup minimal, gunakan itu. Jika migrasi memerlukan hubungan atau interaksi kompleks lainnya, saya lebih suka menggunakan kekuatan penuh model dan sesi seperti yang dijelaskan di bawah ini.

Berikut ini adalah contoh skrip migrasi yang menyiapkan beberapa model deklaratif yang akan digunakan untuk memanipulasi data dalam sebuah sesi. Poin utamanya adalah:

  1. Tentukan model dasar yang Anda butuhkan, dengan kolom yang Anda perlukan. Anda tidak membutuhkan setiap kolom, cukup kunci utama dan yang akan Anda gunakan.

  2. Dalam fungsi upgrade, gunakan op.get_bind()untuk mendapatkan koneksi saat ini, dan buat sesi dengannya.

    • Atau gunakan bind.execute()untuk menggunakan level SQLAlchemy yang lebih rendah untuk menulis kueri SQL secara langsung. Ini berguna untuk migrasi sederhana.
  3. Gunakan model dan sesi seperti biasa dalam aplikasi Anda.

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

Migrasi menentukan model terpisah karena model dalam kode Anda mewakili status database saat ini, sedangkan migrasi mewakili langkah-langkah selama proses . Database Anda mungkin berada dalam status apa pun di sepanjang jalur tersebut, sehingga model mungkin belum disinkronkan dengan database. Kecuali jika Anda sangat berhati-hati, menggunakan model nyata secara langsung akan menyebabkan masalah dengan kolom yang hilang, data tidak valid, dll. Lebih jelas menyatakan secara eksplisit kolom dan model apa yang akan Anda gunakan dalam migrasi.

davidisme
sumber
12

Anda juga dapat menggunakan SQL langsung, lihat ( Referensi Operasi Alembik ) seperti pada contoh berikut:

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 ###
Martlark
sumber
Dalam kasus saya selalu ingin membaca pernyataan SQL dari file eksternal dan kemudian menyebarkannya ke op.executedalam upgrade(), apakah ada cara untuk memberikan template default yang akan digunakan oleh alembic revisionperintah (badan standar untuk dihasilkan .pyfile)?
Quentin
1
Saya tidak tahu @Quentin. Itu ide yang menarik.
Martlark
7

Saya merekomendasikan penggunaan pernyataan inti SQLAlchemy menggunakan tabel ad-hoc, seperti yang dijelaskan dalam dokumentasi resmi , karena memungkinkan penggunaan SQL agnostik dan penulisan pythonic dan juga mandiri. SQLAlchemy Core adalah yang terbaik dari kedua dunia untuk skrip migrasi.

Berikut adalah contoh konsepnya:

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
cmc
sumber