From b0fd1d89ea709dd4dffdd8c3ec880b77e6fb3d62 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Thu, 5 Mar 2026 17:33:32 -0600 Subject: [PATCH] fix: eliminate N+1 queries in batch POST endpoints (#25) Replace per-row Team/Player lookups with bulk IN-list queries before the validation loop in post_transactions, post_results, post_schedules, and post_batstats. A 50-move batch now uses 2 queries instead of 150. Co-Authored-By: Claude Sonnet 4.6 --- app/routers_v3/battingstats.py | 15 +++++++++++---- app/routers_v3/results.py | 13 +++++++++++-- app/routers_v3/schedules.py | 13 +++++++++++-- app/routers_v3/transactions.py | 17 ++++++++++++++--- 4 files changed, 47 insertions(+), 11 deletions(-) diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index 68df4d5..87d944f 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -381,14 +381,21 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme) all_stats = [] + all_team_ids = list(set(x.team_id for x in s_list.stats)) + all_player_ids = list(set(x.player_id for x in s_list.stats)) + found_team_ids = set( + t.id for t in Team.select(Team.id).where(Team.id << all_team_ids) + ) + found_player_ids = set( + p.id for p in Player.select(Player.id).where(Player.id << all_player_ids) + ) + for x in s_list.stats: - team = Team.get_or_none(Team.id == x.team_id) - this_player = Player.get_or_none(Player.id == x.player_id) - if team is None: + if x.team_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.team_id} not found" ) - if this_player is None: + if x.player_id not in found_player_ids: raise HTTPException( status_code=404, detail=f"Player ID {x.player_id} not found" ) diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py index 76bd440..aa95550 100644 --- a/app/routers_v3/results.py +++ b/app/routers_v3/results.py @@ -159,12 +159,21 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche raise HTTPException(status_code=401, detail="Unauthorized") new_results = [] + + all_team_ids = list( + set(x.awayteam_id for x in result_list.results) + | set(x.hometeam_id for x in result_list.results) + ) + found_team_ids = set( + t.id for t in Team.select(Team.id).where(Team.id << all_team_ids) + ) + for x in result_list.results: - if Team.get_or_none(Team.id == x.awayteam_id) is None: + if x.awayteam_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.awayteam_id} not found" ) - if Team.get_or_none(Team.id == x.hometeam_id) is None: + if x.hometeam_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.hometeam_id} not found" ) diff --git a/app/routers_v3/schedules.py b/app/routers_v3/schedules.py index a7bca82..e5523e5 100644 --- a/app/routers_v3/schedules.py +++ b/app/routers_v3/schedules.py @@ -144,12 +144,21 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s raise HTTPException(status_code=401, detail="Unauthorized") new_sched = [] + + all_team_ids = list( + set(x.awayteam_id for x in sched_list.schedules) + | set(x.hometeam_id for x in sched_list.schedules) + ) + found_team_ids = set( + t.id for t in Team.select(Team.id).where(Team.id << all_team_ids) + ) + for x in sched_list.schedules: - if Team.get_or_none(Team.id == x.awayteam_id) is None: + if x.awayteam_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.awayteam_id} not found" ) - if Team.get_or_none(Team.id == x.hometeam_id) is None: + if x.hometeam_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.hometeam_id} not found" ) diff --git a/app/routers_v3/transactions.py b/app/routers_v3/transactions.py index 4d11e3a..29fbaa6 100644 --- a/app/routers_v3/transactions.py +++ b/app/routers_v3/transactions.py @@ -143,16 +143,27 @@ async def post_transactions( all_moves = [] + all_team_ids = list( + set(x.oldteam_id for x in moves.moves) | set(x.newteam_id for x in moves.moves) + ) + all_player_ids = list(set(x.player_id for x in moves.moves)) + found_team_ids = set( + t.id for t in Team.select(Team.id).where(Team.id << all_team_ids) + ) + found_player_ids = set( + p.id for p in Player.select(Player.id).where(Player.id << all_player_ids) + ) + for x in moves.moves: - if Team.get_or_none(Team.id == x.oldteam_id) is None: + if x.oldteam_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.oldteam_id} not found" ) - if Team.get_or_none(Team.id == x.newteam_id) is None: + if x.newteam_id not in found_team_ids: raise HTTPException( status_code=404, detail=f"Team ID {x.newteam_id} not found" ) - if Player.get_or_none(Player.id == x.player_id) is None: + if x.player_id not in found_player_ids: raise HTTPException( status_code=404, detail=f"Player ID {x.player_id} not found" )