fix: batch standings updates to eliminate N+1 queries in recalculate (#75) #93
No reviewers
Labels
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: cal/major-domo-database#93
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "issue/75-fix-n-1-query-pattern-in-standings-recalculation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #75
Summary
Standings.recalculate()was callinggame.update_standings()per game, which issued ~4 DB queries each (twoStandings.get_season()SELECTs + twoDivision.get_by_id()SELECTs + two saves). For a full season (~288 games) this produced 1,100+ round-trips to PostgreSQL.Changes
app/db_engine.py—Standings.recalculate():standings_by_team_iddict (1 query)teams_by_iddict (1 query)divisions_by_iddict (1 query)update_standings()using dict lookups (0 queries per game)home_stan.save()/away_stan.save()calls with a singleStandings.bulk_update()at the end (1 query)Result
Full-season recalculation: ~1,100+ queries → ~5 queries
Notes
StratGame.update_standings()is left intact — it is the correct interface for single-game updates outside of a full recalculationgame.away_team_id/game.home_team_idaccess the raw FK integer without lazy-loadingcontinue) — matches the implicit behavior of the original codeAI Code Review
Files Reviewed
app/db_engine.py(modified —Standings.recalculate())Findings
Correctness
StratGame.update_standings()implementation at lines 1995–2099. Both branches (home win / away win) verified field-by-field against the original.game.away_team_id,game.home_team_id,away_team_obj.division_id,s.team_id) is the correct Peewee idiom — no lazy-load queries triggered during the loop.bulk_updatefields list covers all fields modified by the tallying loop.pythag_wins,pythag_losses,last8_wins,last8_lossesare intentionally excluded — they're set by the separateteam.run_pythag_last8()pass that follows.standings_by_team_id(no teams with divisions):bulk_update([])generates no SQL — safe.bulk_updatewith their accumulated values (zero contribution from skipped game), matching implicit original behaviour.if full_wipe:block — consistent with original;full_wipe=Falsepath is unaffected.Security
Style & Conventions
db.atomic()wrapper, in-memory dict lookups).Suggestions
Verdict: APPROVED
Clean, faithful refactor. The inlined win/loss tallying matches
update_standings()exactly, raw FK access avoids implicit queries, and thebulk_updatefields list is complete. No logic changes, no regressions.Automated review by Claude PR Reviewer (posted as COMMENT — Gitea blocks self-approval)
Checkout
From your project repository, check out a new branch and test the changes.