From acac856b616849162a4cbacd38deffdad27baa28 Mon Sep 17 00:00:00 2001 From: shangfangjian Date: Sun, 20 Sep 2026 20:35:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=AF=E5=88=86=E6=A6=9C?= =?UTF-8?q?=E5=85=A5=E5=BA=93=20UniqueViolationError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:ingest_bzzoiro_standings 批量处理时逐行 SELECT 检查 Standing 是否存在, 但新 INSERT 的 Standing 未 flush 就被同批下一条 SELECT 判定为不存在, 导致同一 (league_id, season, team_id) 插入两次 → UniqueViolation。 修复: - 预加载该 league+season 的所有已有 standings team_id 到内存 set - 同批内按 team.id 去重,同一 team 仅处理一次 - 命中已有 standing 时 UPDATE,否则 INSERT 并立即加入 set 防止同批重复 Co-Authored-By: new-provider/LongCat-2.0 <> --- src/data/bzzoiro.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/src/data/bzzoiro.py b/src/data/bzzoiro.py index 8a46efc..de88e06 100644 --- a/src/data/bzzoiro.py +++ b/src/data/bzzoiro.py @@ -417,7 +417,16 @@ async def ingest_bzzoiro_standings(db, *, leagues: Iterable[str], season: str | for t in (await db.execute(stmt)).scalars(): team_map[t.name] = t + # 预加载该 league+season 已有的 standings(避免同批内重复 INSERT 导致 UniqueViolation) + existing_standings: set[int] = set() + stmt = select(Standing.team_id).where( + Standing.league_id == league.id, Standing.season == season_label, + ) + for (tid,) in (await db.execute(stmt)).all(): + existing_standings.add(tid) + now = datetime.now(timezone.utc) + seen_teams: set[int] = set() # 同批内去重:同一 team 只处理一次 for r in rows: team_name = normalize_name(str(r.get("team_name", ""))) if not team_name: @@ -430,6 +439,11 @@ async def ingest_bzzoiro_standings(db, *, leagues: Iterable[str], season: str | team_map[team_name] = team league_r["teams_created"] += 1 + # 同批内同一 team 仅处理第一次 + if team.id in seen_teams: + continue + seen_teams.add(team.id) + zone = r.get("zone") or {} values = dict( position=_to_int_or_none(r.get("position")) or 0, @@ -449,20 +463,22 @@ async def ingest_bzzoiro_standings(db, *, leagues: Iterable[str], season: str | retrieved_at=now, ) - stmt = select(Standing).where( - Standing.league_id == league.id, - Standing.season == season_label, - Standing.team_id == team.id, - ) - standing = (await db.execute(stmt)).scalar_one_or_none() - if standing is None: - standing = Standing( - league_id=league.id, season=season_label, team_id=team.id, **values + if team.id in existing_standings: + # 已有 → 仍需 UPDATE:回查对象(少量,可接受) + stmt = select(Standing).where( + Standing.league_id == league.id, + Standing.season == season_label, + Standing.team_id == team.id, ) - db.add(standing) + standing = (await db.execute(stmt)).scalar_one_or_none() + if standing: + for k, v in values.items(): + setattr(standing, k, v) else: - for k, v in values.items(): - setattr(standing, k, v) + db.add(Standing( + league_id=league.id, season=season_label, team_id=team.id, **values, + )) + existing_standings.add(team.id) # 防止同批内重复 league_r["upserted"] += 1 league_r["rows"] = len(rows)