Як виконати вставки та оновлення в сценарії оновлення Alembic?


95

Мені потрібно змінити дані під час оновлення 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')
    )

Я хочу представити таблицю "команд". Я створив другу редакцію:

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

Я хотів би, щоб у другій міграції були також додані такі дані:

  1. Таблиця груп населення:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
  2. Оновіть player.team_id на основі імені players.team:

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

Як виконати вставки та оновлення всередині сценарію оновлення?

Відповіді:


147

Ви вимагаєте міграції даних , на відміну від міграції схеми, яка найбільш поширена в документах Alembic.

Ця відповідь передбачає, що ви використовуєте декларатив (на відміну від 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')

Міграція визначає окремі моделі, оскільки моделі у вашому коді представляють поточний стан бази даних, тоді як міграції представляють кроки на шляху . Ваша база даних може знаходитись у будь-якому стані на цьому шляху, тому моделі можуть ще не синхронізуватися з базою даних. Якщо ви не будете дуже обережними, безпосереднє використання реальних моделей призведе до проблем із відсутніми стовпцями, недійсними даними тощо. Ясніше чітко вказати, які саме стовпці та моделі ви будете використовувати при міграції.


11

Ви також можете використовувати прямий 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 ###

Якщо я завжди хотів прочитати оператор SQL із зовнішнього файлу, а потім передати його op.executeв upgrade(), чи є спосіб надати шаблон за замовчуванням, який буде використовуватися alembic revisionкомандою (тіло за замовчуванням для згенерованого .pyфайлу)?
Квентін,

1
Я не знаю @Quentin. Це цікава ідея.
Мартларк

6

Я рекомендую використовувати основні оператори SQLAlchemy, використовуючи спеціальну таблицю, як це докладно описано в офіційній документації , оскільки це дозволяє використовувати агностичний SQL та пітонічний запис, а також є автономним. 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.