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. players.team 이름에 따라 players.team_id를 업데이트하십시오.

    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.executeupgrade()의해 사용되는 기본 템플릿을 제공하는 방법이 alembic revision명령 (생성을위한 기본 본문 .py파일)?
Quentin

1
나는 @Quentin을 모른다. 흥미로운 아이디어입니다.
Martlark

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.