2014-07-07 35 views
37

我需要在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') 
    ) 

我想介紹一個「團隊」表。我創建了一個第二次修訂:

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. 更新的球員。 team_id基於球員。隊名:

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

如何在升級腳本內執行插入和更新?

回答

65

你所要求的是一個數據遷移,而不是在架構遷移這是最普遍的蒸餾器文檔。

這個答案假定你使用聲明式(而不是class-Mapper-Table或core)來定義你的模型。將其適用於其他形式應該相對簡單。

請注意,Alembic提供了一些基本的數據功能:op.bulk_insert()op.execute()。如果操作相當簡單,請使用這些操作。如果遷移需要關係或其他複雜的交互,我傾向於使用模型和會話的全部功能,如下所述。

以下是一個遷移腳本示例,它設置了一些將用於操作會話中數據的聲明性模型。關鍵點是:

  1. 定義您需要的基本模型,包含您需要的列。你不需要每一列,只需要主鍵和你將要使用的那些。
  2. 在升級功能中,使用op.get_bind()獲取當前連接並與其建立會話。
  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 
from flask_sqlalchemy import _SessionSignalEvents 
import sqlalchemy as sa 
from sqlalchemy import event 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker, Session as BaseSession, relationship 

Session = sessionmaker() 

event.remove(BaseSession, 'before_commit', _SessionSignalEvents.session_signal_before_commit) 
event.remove(BaseSession, 'after_commit', _SessionSignalEvents.session_signal_after_commit) 
event.remove(BaseSession, 'after_rollback', _SessionSignalEvents.session_signal_after_rollback) 

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

event.remove線應對瓶,SQLAlchemy的。該擴展會將一些事件添加到會話中,但由於遷移發生在應用上下文之外,因此這些事件會產生異常。如果您沒有使用該擴展名或正在使用版本> = 2.0(尚未發佈),則不需要這些行(或相關導入)。

+0

什麼可能阻止你使用alembic遷移中的現有應用程序模型? –

+17

@AndriyYurchuk問題在於模型表示數據庫的*當前狀態*,而遷移表示沿途的*步驟*。但是您的數據庫可能處於該路徑的任何狀態,因此模型可能無法與數據庫同步。除非你非常小心(這使我以不可預知的方式咬人),直接使用這些模型會導致列缺失,數據無效等問題。清楚地明確說明你將使用哪些列和模型移民。 – davidism

+6

@AndriyYurchuk我避免使用模型,除非我要做複雜的數據遷移,涉及查詢跨關係。對於簡單的遷移,只要你願意直接寫SQL,總是會有'op.execute'。 – davidism

相關問題