Làm cách nào để thực hiện chèn và cập nhật trong tập lệnh nâng cấp Alembic?


95

Tôi cần thay đổi dữ liệu trong quá trình nâng cấp Alembic.

Tôi hiện có bảng 'người chơi' trong bản sửa đổi đầu tiên:

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

Tôi muốn giới thiệu một bảng 'các đội'. Tôi đã tạo bản sửa đổi thứ hai:

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

Tôi muốn lần di chuyển thứ hai cũng thêm dữ liệu sau:

  1. Điền bảng đội:

    INSERT INTO teams (name) SELECT DISTINCT team FROM players;
  2. Cập nhật player.team_id dựa trên tên player.team:

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

Làm cách nào để thực hiện chèn và cập nhật bên trong tập lệnh nâng cấp?

Câu trả lời:


147

Những gì bạn đang yêu cầu là di chuyển dữ liệu , trái ngược với di chuyển giản đồ phổ biến nhất trong tài liệu Alembic.

Câu trả lời này giả sử bạn đang sử dụng khai báo (trái ngược với class-Mapper-Table hoặc core) để xác định các mô hình của mình. Nó sẽ tương đối đơn giản để điều chỉnh điều này với các hình thức khác.

Lưu ý rằng Alembic cung cấp một số chức năng dữ liệu cơ bản: op.bulk_insert()op.execute(). Nếu các hoạt động khá tối thiểu, hãy sử dụng chúng. Nếu quá trình di chuyển yêu cầu các mối quan hệ hoặc các tương tác phức tạp khác, tôi muốn sử dụng toàn bộ sức mạnh của các mô hình và phiên như được mô tả bên dưới.

Sau đây là một ví dụ về tập lệnh di chuyển thiết lập một số mô hình khai báo sẽ được sử dụng để thao tác dữ liệu trong một phiên. Các điểm chính là:

  1. Xác định các mô hình cơ bản bạn cần, với các cột bạn sẽ cần. Bạn không cần mọi cột, chỉ cần khóa chính và những cột bạn sẽ sử dụng.

  2. Trong chức năng nâng cấp, sử dụng op.get_bind()để lấy kết nối hiện tại và thực hiện một phiên với nó.

    • Hoặc sử dụng bind.execute()để sử dụng SQLAlchemy cấp thấp hơn để viết các truy vấn SQL trực tiếp. Điều này rất hữu ích cho việc di chuyển đơn giản.
  3. Sử dụng các mô hình và phiên như bạn thường làm trong ứng dụng của mình.

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

Quá trình di chuyển xác định các mô hình riêng biệt vì các mô hình trong mã của bạn đại diện cho trạng thái hiện tại của cơ sở dữ liệu, trong khi quá trình di chuyển đại diện cho các bước trong quá trình thực hiện . Cơ sở dữ liệu của bạn có thể ở bất kỳ trạng thái nào dọc theo đường dẫn đó, vì vậy các mô hình có thể chưa đồng bộ hóa với cơ sở dữ liệu. Trừ khi bạn rất cẩn thận, việc sử dụng trực tiếp các mô hình thực sẽ gây ra vấn đề với các cột bị thiếu, dữ liệu không hợp lệ, v.v. Rõ ràng hơn là bạn phải trình bày rõ ràng chính xác những cột và mô hình nào bạn sẽ sử dụng trong quá trình di chuyển.


11

Bạn cũng có thể sử dụng SQL trực tiếp xem ( Tham chiếu hoạt động Alembic ) như trong ví dụ sau:

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

Trong trường hợp tôi luôn muốn đọc câu lệnh SQL từ một tệp bên ngoài và sau đó chuyển nó op.executevào upgrade(), có cách nào để cung cấp mẫu mặc định được sử dụng bằng alembic revisionlệnh (nội dung mặc định cho .pytệp được tạo ) không?
Quentin

1
Tôi không biết @Quentin. Đó là một ý tưởng thú vị.
Martlark

6

Tôi khuyên bạn nên sử dụng các câu lệnh cốt lõi của SQLAlchemy bằng cách sử dụng bảng đặc biệt, như được nêu chi tiết trong tài liệu chính thức , vì nó cho phép sử dụng SQL bất khả tri và cách viết pythonic và cũng độc lập. SQLAlchemy Core là tốt nhất của cả hai thế giới cho các tập lệnh di chuyển.

Đây là một ví dụ về khái niệm:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.