Postgres Query Updates

Fixing query errors caused by Postgres vs SQLite
This commit is contained in:
Cal Corum 2025-08-25 07:19:13 -05:00
parent 7130a1fd43
commit 8c492273dc
7 changed files with 163 additions and 60 deletions

View File

@ -18,6 +18,7 @@ WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
libpq-dev \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
@ -38,7 +39,7 @@ RUN mkdir -p /usr/src/app/storage /usr/src/app/logs && \
# Health check for container monitoring
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/health || exit 1
CMD curl -f http://localhost:80/api/v3/current || exit 1
# Switch to non-root user
USER sba

View File

@ -10,9 +10,7 @@ from fastapi.openapi.utils import get_openapi
# from fastapi.openapi.docs import get_swagger_ui_html
# from fastapi.openapi.utils import get_openapi
from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \
pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers, stratgame, stratplay, \
injuries, decisions, divisions, sbaplayers, custom_commands
from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers, stratgame, stratplay, injuries, decisions, divisions, sbaplayers, custom_commands, views
# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else logging.WARNING
@ -25,9 +23,9 @@ logger = logging.getLogger('discord_app')
logger.setLevel(log_level)
handler = RotatingFileHandler(
filename='/tmp/sba-database.log',
filename='./logs/sba-database.log',
# encoding='utf-8',
maxBytes=32 * 1024 * 1024, # 32 MiB
maxBytes=8 * 1024 * 1024, # 8 MiB
backupCount=5, # Rotate through 5 files
)
@ -69,6 +67,7 @@ app.include_router(decisions.router)
app.include_router(divisions.router)
app.include_router(sbaplayers.router)
app.include_router(custom_commands.router)
app.include_router(views.router)
logger.info(f'Loaded all routers.')

View File

@ -142,9 +142,23 @@ async def get_totalstats(
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.')
# Build SELECT fields conditionally based on group_by to match GROUP BY exactly
select_fields = []
if group_by == 'player':
select_fields = [BattingStat.player]
elif group_by == 'team':
select_fields = [BattingStat.team]
elif group_by == 'playerteam':
select_fields = [BattingStat.player, BattingStat.team]
else:
# Default case
select_fields = [BattingStat.player]
all_stats = (
BattingStat
.select(BattingStat.player, fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'),
.select(*select_fields,
fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'),
fn.SUM(BattingStat.run).alias('sum_run'), fn.SUM(BattingStat.hit).alias('sum_hit'),
fn.SUM(BattingStat.rbi).alias('sum_rbi'), fn.SUM(BattingStat.double).alias('sum_double'),
fn.SUM(BattingStat.triple).alias('sum_triple'), fn.SUM(BattingStat.hr).alias('sum_hr'),
@ -159,8 +173,7 @@ async def get_totalstats(
fn.SUM(BattingStat.error).alias('sum_error'), fn.SUM(BattingStat.pb).alias('sum_pb'),
fn.SUM(BattingStat.sbc).alias('sum_sbc'), fn.SUM(BattingStat.csc).alias('sum_csc'),
fn.SUM(BattingStat.roba).alias('sum_roba'), fn.SUM(BattingStat.robs).alias('sum_robs'),
fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto'),
BattingStat.team)
fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto'))
.where(BattingStat.season == season)
.having(fn.SUM(BattingStat.pa) >= min_pa)
)
@ -201,12 +214,8 @@ async def get_totalstats(
elif sort == 'team':
all_stats = all_stats.order_by(BattingStat.team)
if group_by is not None:
if group_by == 'team':
all_stats = all_stats.group_by(BattingStat.team)
elif group_by == 'player':
all_stats = all_stats.group_by(BattingStat.player)
elif group_by == 'playerteam':
all_stats = all_stats.group_by(BattingStat.team, BattingStat.player)
# Use the same fields for GROUP BY as we used for SELECT
all_stats = all_stats.group_by(*select_fields)
# if team_abbrev is None and team_id is None and player_name is None and player_id is None:
# raise HTTPException(
@ -230,9 +239,23 @@ async def get_totalstats(
return_stats = {
'count': all_stats.count(),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
'stats': []
}
for x in all_stats:
# Handle player field based on grouping with safe access
this_player = 'TOT'
if 'player' in group_by and hasattr(x, 'player'):
this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False)
# Handle team field based on grouping with safe access
this_team = 'TOT'
if 'team' in group_by and hasattr(x, 'team'):
this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False)
return_stats['stats'].append({
'player': this_player,
'team': this_team,
'pa': x.sum_pa,
'ab': x.sum_ab,
'run': x.sum_run,
@ -253,8 +276,7 @@ async def get_totalstats(
'bpfo': x.sum_bpfo,
'bp1b': x.sum_bp1b,
'bplo': x.sum_bplo
} for x in all_stats]
}
})
db.close()
return return_stats

View File

@ -110,12 +110,26 @@ async def get_totalstats(
group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False,
min_ch: Optional[int] = 1, week: list = Query(default=None)):
# Build SELECT fields conditionally based on group_by to match GROUP BY exactly
select_fields = []
if group_by == 'player':
select_fields = [BattingStat.player, BattingStat.pos]
elif group_by == 'team':
select_fields = [BattingStat.team, BattingStat.pos]
elif group_by == 'playerteam':
select_fields = [BattingStat.player, BattingStat.team, BattingStat.pos]
else:
# Default case
select_fields = [BattingStat.player, BattingStat.pos]
all_stats = (
BattingStat
.select(BattingStat.player, BattingStat.pos, fn.SUM(BattingStat.xch).alias('sum_xch'),
.select(*select_fields,
fn.SUM(BattingStat.xch).alias('sum_xch'),
fn.SUM(BattingStat.xhit).alias('sum_xhit'), fn.SUM(BattingStat.error).alias('sum_error'),
fn.SUM(BattingStat.pb).alias('sum_pb'), fn.SUM(BattingStat.sbc).alias('sum_sbc'),
fn.SUM(BattingStat.csc).alias('sum_csc'), BattingStat.team)
fn.SUM(BattingStat.csc).alias('sum_csc'))
.where(BattingStat.season == season)
.having(fn.SUM(BattingStat.xch) >= min_ch)
)
@ -157,12 +171,8 @@ async def get_totalstats(
elif sort == 'team':
all_stats = all_stats.order_by(BattingStat.team)
if group_by is not None:
if group_by == 'team':
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team)
elif group_by == 'player':
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.player)
elif group_by == 'playerteam':
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team, BattingStat.player)
# Use the same fields for GROUP BY as we used for SELECT
all_stats = all_stats.group_by(*select_fields)
if team_id is not None:
all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(BattingStat.team << all_teams)
@ -178,10 +188,27 @@ async def get_totalstats(
all_stats = all_stats.where(BattingStat.player << all_players)
return_stats = {
'count': sum(1 for i in all_stats if i.sum_xch + i.sum_sbc > 0),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
'count': 0,
'stats': []
}
for x in all_stats:
if x.sum_xch + x.sum_sbc <= 0:
continue
# Handle player field based on grouping with safe access
this_player = 'TOT'
if 'player' in group_by and hasattr(x, 'player'):
this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False)
# Handle team field based on grouping with safe access
this_team = 'TOT'
if 'team' in group_by and hasattr(x, 'team'):
this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False)
return_stats['stats'].append({
'player': this_player,
'team': this_team,
'pos': x.pos,
'xch': x.sum_xch,
'xhit': x.sum_xhit,
@ -189,7 +216,8 @@ async def get_totalstats(
'pb': x.sum_pb,
'sbc': x.sum_sbc,
'csc': x.sum_csc
} for x in all_stats if x.sum_xch + x.sum_sbc > 0]
}
})
return_stats['count'] = len(return_stats['stats'])
db.close()
return return_stats

View File

@ -129,9 +129,23 @@ async def get_totalstats(
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.')
# Build SELECT fields conditionally based on group_by to match GROUP BY exactly
select_fields = []
if group_by == 'player':
select_fields = [PitchingStat.player]
elif group_by == 'team':
select_fields = [PitchingStat.team]
elif group_by == 'playerteam':
select_fields = [PitchingStat.player, PitchingStat.team]
else:
# Default case
select_fields = [PitchingStat.player]
all_stats = (
PitchingStat
.select(PitchingStat.player, fn.SUM(PitchingStat.ip).alias('sum_ip'),
.select(*select_fields,
fn.SUM(PitchingStat.ip).alias('sum_ip'),
fn.SUM(PitchingStat.hit).alias('sum_hit'), fn.SUM(PitchingStat.run).alias('sum_run'),
fn.SUM(PitchingStat.erun).alias('sum_erun'), fn.SUM(PitchingStat.so).alias('sum_so'),
fn.SUM(PitchingStat.bb).alias('sum_bb'), fn.SUM(PitchingStat.hbp).alias('sum_hbp'),
@ -140,8 +154,7 @@ async def get_totalstats(
fn.SUM(PitchingStat.win).alias('sum_win'), fn.SUM(PitchingStat.loss).alias('sum_loss'),
fn.SUM(PitchingStat.hold).alias('sum_hold'), fn.SUM(PitchingStat.sv).alias('sum_sv'),
fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.SUM(PitchingStat.irs).alias('sum_irs'),
fn.SUM(PitchingStat.gs).alias('sum_gs'), fn.COUNT(PitchingStat.game).alias('sum_games'),
PitchingStat.team)
fn.SUM(PitchingStat.gs).alias('sum_gs'), fn.COUNT(PitchingStat.game).alias('sum_games'))
.where(PitchingStat.season == season)
.having(fn.SUM(PitchingStat.ip) >= ip_min)
)
@ -181,12 +194,8 @@ async def get_totalstats(
elif sort == 'team':
all_stats = all_stats.order_by(PitchingStat.team)
if group_by is not None:
if group_by == 'team':
all_stats = all_stats.group_by(PitchingStat.team)
elif group_by == 'player':
all_stats = all_stats.group_by(PitchingStat.player)
elif group_by == 'playerteam':
all_stats = all_stats.group_by(PitchingStat.team, PitchingStat.player)
# Use the same fields for GROUP BY as we used for SELECT
all_stats = all_stats.group_by(*select_fields)
if team_id is not None:
all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(PitchingStat.team << all_teams)
@ -202,9 +211,23 @@ async def get_totalstats(
return_stats = {
'count': all_stats.count(),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
'stats': []
}
for x in all_stats:
# Handle player field based on grouping with safe access
this_player = 'TOT'
if 'player' in group_by and hasattr(x, 'player'):
this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False)
# Handle team field based on grouping with safe access
this_team = 'TOT'
if 'team' in group_by and hasattr(x, 'team'):
this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False)
return_stats['stats'].append({
'player': this_player,
'team': this_team,
'ip': x.sum_ip,
'hit': x.sum_hit,
'run': x.sum_run,
@ -224,8 +247,7 @@ async def get_totalstats(
'hold': x.sum_hold,
'sv': x.sum_sv,
'bsv': x.sum_bsv
} for x in all_stats]
}
})
db.close()
return return_stats

View File

@ -98,7 +98,7 @@ async def get_players(
line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5, line.pos_6, line.pos_7, line.pos_8,
line.last_game, line.last_game2, line.il_return, line.demotion_week, line.headshot,
line.vanity_card, line.strat_code.replace(",", "-_-") if line.strat_code is not None else "",
line.bbref_id, line.injury_rating, line.id, line.sbaplayer_id
line.bbref_id, line.injury_rating, line.id, line.sbaplayer
]
)
return_players = {

View File

@ -416,17 +416,26 @@ async def get_batting_totals(
if inning is not None:
bat_plays = bat_plays.where(StratPlay.inning_num << inning)
# Initialize game_select_fields for use in GROUP BY
game_select_fields = []
# Add StratPlay.game to SELECT clause for group_by scenarios that need it
if group_by in ['playergame', 'teamgame']:
# Rebuild the query with StratPlay.game included
# For playergame/teamgame grouping, build appropriate SELECT fields
if group_by == 'playergame':
game_select_fields = [StratPlay.batter, StratPlay.game, StratPlay.batter_team]
else: # teamgame
game_select_fields = [StratPlay.batter_team, StratPlay.game]
game_bat_plays = (
StratPlay
.select(StratPlay.batter, StratPlay.game, fn.SUM(StratPlay.pa).alias('sum_pa'),
.select(*game_select_fields,
fn.SUM(StratPlay.pa).alias('sum_pa'),
fn.SUM(StratPlay.ab).alias('sum_ab'), fn.SUM(StratPlay.run).alias('sum_run'),
fn.SUM(StratPlay.hit).alias('sum_hit'), fn.SUM(StratPlay.rbi).alias('sum_rbi'),
fn.SUM(StratPlay.double).alias('sum_double'), fn.SUM(StratPlay.triple).alias('sum_triple'),
fn.SUM(StratPlay.homerun).alias('sum_hr'), fn.SUM(StratPlay.bb).alias('sum_bb'),
fn.SUM(StratPlay.so).alias('sum_so'), StratPlay.batter_team,
fn.SUM(StratPlay.so).alias('sum_so'),
fn.SUM(StratPlay.hbp).alias('sum_hbp'), fn.SUM(StratPlay.sac).alias('sum_sac'),
fn.SUM(StratPlay.ibb).alias('sum_ibb'), fn.SUM(StratPlay.gidp).alias('sum_gidp'),
fn.SUM(StratPlay.sb).alias('sum_sb'), fn.SUM(StratPlay.cs).alias('sum_cs'),
@ -489,10 +498,16 @@ async def get_batting_totals(
run_plays = run_plays.group_by(StratPlay.runner, StratPlay.runner_team)
def_plays = def_plays.group_by(StratPlay.defender, StratPlay.defender_team)
elif group_by == 'playergame':
if game_select_fields:
bat_plays = bat_plays.group_by(*game_select_fields)
else:
bat_plays = bat_plays.group_by(StratPlay.batter, StratPlay.game)
run_plays = run_plays.group_by(StratPlay.runner, StratPlay.game)
def_plays = def_plays.group_by(StratPlay.defender, StratPlay.game)
elif group_by == 'teamgame':
if game_select_fields:
bat_plays = bat_plays.group_by(*game_select_fields)
else:
bat_plays = bat_plays.group_by(StratPlay.batter_team, StratPlay.game)
run_plays = run_plays.group_by(StratPlay.runner_team, StratPlay.game)
elif group_by == 'league':
@ -532,11 +547,19 @@ async def get_batting_totals(
elif sort == 'pa-asc':
bat_plays = bat_plays.order_by(SQL('sum_pa').asc())
elif sort == 'newest':
bat_plays = bat_plays.order_by(StratPlay.game_id.desc(), StratPlay.play_num.desc())
run_plays = run_plays.order_by(StratPlay.game_id.desc(), StratPlay.play_num.desc())
# For grouped queries, only sort by fields in GROUP BY clause
if group_by in ['playergame', 'teamgame']:
# StratPlay.game is in GROUP BY for these cases
bat_plays = bat_plays.order_by(StratPlay.game.desc())
run_plays = run_plays.order_by(StratPlay.game.desc())
# For other group_by values, skip game_id/play_num sorting since they're not in GROUP BY
elif sort == 'oldest':
bat_plays = bat_plays.order_by(StratPlay.game_id, StratPlay.play_num)
run_plays = run_plays.order_by(StratPlay.game_id, StratPlay.play_num)
# For grouped queries, only sort by fields in GROUP BY clause
if group_by in ['playergame', 'teamgame']:
# StratPlay.game is in GROUP BY for these cases
bat_plays = bat_plays.order_by(StratPlay.game.asc())
run_plays = run_plays.order_by(StratPlay.game.asc())
# For other group_by values, skip game_id/play_num sorting since they're not in GROUP BY
if limit < 1:
limit = 1
@ -1078,9 +1101,17 @@ async def get_fielding_totals(
elif sort == 'ch-asc':
def_plays = def_plays.order_by(SQL('sum_chances').asc())
elif sort == 'newest':
def_plays = def_plays.order_by(StratPlay.game_id.desc(), StratPlay.play_num.desc())
# For grouped queries, only sort by fields in GROUP BY clause
if group_by in ['playergame', 'playerpositiongame', 'playerweek', 'teamweek']:
# StratPlay.game is in GROUP BY for these cases
def_plays = def_plays.order_by(StratPlay.game.desc())
# For other group_by values, skip game_id/play_num sorting since they're not in GROUP BY
elif sort == 'oldest':
def_plays = def_plays.order_by(StratPlay.game_id, StratPlay.play_num)
# For grouped queries, only sort by fields in GROUP BY clause
if group_by in ['playergame', 'playerpositiongame', 'playerweek', 'teamweek']:
# StratPlay.game is in GROUP BY for these cases
def_plays = def_plays.order_by(StratPlay.game.asc())
# For other group_by values, skip game_id/play_num sorting since they're not in GROUP BY
if limit < 1:
limit = 1