2014-07-07 38 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

相关问题