"""P0-02: 积分榜改为追加快照(append-only) + available_at 去掉 uq_standings_league_season_team(league,season,team 唯一), 改为 (league, season, team, available_at) 唯一; 每次采集 INSERT 新行(available_at=now),支持回测还原历史榜单。 Revision ID: 0023_standings_append_only Revises: 0022_match_score_status Create Date: 2026-09-22 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa revision: str = '0023_standings_append_only' down_revision: Union[str, None] = '0022_match_score_status' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: from datetime import datetime, timezone # 1) 加 available_at 列(非空,默认 now;存量回填 retrieved_at 或 now) op.add_column( 'standings', sa.Column('available_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), ) # 存量行: available_at 取 retrieved_at(若存在)否则 now op.execute("UPDATE standings SET available_at = COALESCE(retrieved_at, NOW())") # 2) 去旧唯一约束,加新唯一约束(league, season, team, available_at) op.drop_constraint('uq_standings_league_season_team', 'standings', type_='unique') op.drop_index('ix_standings_leason_season_pos', table_name='standings') op.create_index('ix_standings_league_season_pos', 'standings', ['league_id', 'season', 'position']) op.create_unique_constraint( 'uq_standings_league_season_team_available', 'standings', ['league_id', 'season', 'team_id', 'available_at'], ) op.create_index( 'ix_standings_league_season_team_available', 'standings', ['league_id', 'season', 'team_id', 'available_at'], ) def downgrade() -> None: op.drop_index('ix_standings_league_season_team_available', table_name='standings') op.drop_constraint('uq_standings_league_season_team_available', 'standings', type_='unique') op.drop_index('ix_standings_league_season_pos', table_name='standings') op.create_index('ix_standings_leason_season_pos', 'standings', ['league_id', 'season', 'position']) op.create_unique_constraint( 'uq_standings_league_season_team', 'standings', ['league_id', 'season', 'team_id'], ) op.drop_column('standings', 'available_at')