刷 LeetCode SQL 的题,很多人做到 1308 这道题时会有一个共同感受:题目看懂了,表结构也顺手,但自己一写就发现跟正确答案对不上。这道题原名叫 Running Total for Different Genders,中文一般翻译成“不同性别每日分数总计”,题号 1308,难度中等。它最核心的考点不是 JOIN 也不是子查询,而是你有没有真正理解“每个日期的总计”到底指的是当天独立分数,还是包括之前所有天的累计分数。
如果按后者理解,那这道题就是在考察 SQL 窗口函数里的累计求和写法:SUM(...) OVER (PARTITION BY gender ORDER BY day)。但这里是 LeetCode,表结构里藏着不少细节,只记住一个窗口函数模板是不够的。我当初第一次刷这题就被“同名次、同日期的多个选手”给坑了,跑出来结果居然比预期答案大了好几倍。这篇文章就把 1308 从头到尾拆开讲,包括表结构、题意、错误写法、标准解法,以及我认为真正值得记住的一整套分析套路。
1. 先看需求:1308 到底想算一个什么“总计”
1.1 表结构里的主键设定,直接决定了解题方向
先给表结构。LeetCode 原题里给的 Scores 表类似下面这样:
sql复制CREATE TABLE Scores (
player_name VARCHAR(100),
gender VARCHAR(10),
day DATE,
score_points INT,
PRIMARY KEY (gender, day, player_name)
);
这里最重要的信息是主键是 (gender, day, player_name)。也就是说,同一场比赛里,同一天、同一性别下可能会有多个不同玩家出现。这不是一个“每天每种性别只有一条记录”的简单维度表,而是一张偏明细事实表的表结构。
这一点很关键。很多 SQL 新手刷题时,看到一张表先不关心主键,上来就写 SQL,结果后面错误频出。刷题经验丰富的人拿到表结构第一件事就是确认三件事:
- 表里每一行代表什么粒度?是“一个玩家一天的一场比赛记录”,还是“一个性别在某天的汇总”;
- 有没有可能存在同一个分组键下出现多行的情况;
- 如果存在多行,是先聚合还是先开窗。
在 Scores 表中,粒度就是一个选手在某个日期贡献的一次分数。所以“每个性别每天”要往下收敛一层,分组键是 gender + day,但分组前表里同一个 gender + day 可能有多行。
1.2 “每天的总分”是当日值,还是运行累计值
表结构看完以后,还要看输出要求。这题英文原题叫 Running Total for Different Genders,光看 Running Total 就知道它不是要每天独立分数,而是要“截至某一天为止的历史累计分数”。
题目的输出列是:
text复制gender | day | total
示例表格里也不会明确提醒你这东西叫 running total,但如果你把样例数据手算一遍,会发现每个日期对应的并不是当天新增分数,而是截至当天的累计分数。比如男性在 2020-01-01 当天有两个人参赛,分别得 17 分和 8 分;到了 2020-01-07,又有一个男性得 25 分。这一天的输出 total 不是 25,而是 17 + 8 + 25 = 50。
也就是说,你要计算的满足条件是:
text复制total(某性别, 某天) = 该性别所有 <= 该天的分数之和
弄错这个口径的人,最简单的错误写法就是:
sql复制SELECT gender, day, SUM(score_points) AS total
FROM Scores
GROUP BY gender, day
ORDER BY gender, day;
这样算出来的是每日独立新增,不是累计值。如果题目要求的是每天分组统计,那这样写没问题。但 1308 不是这个意思。
1.3 用样例数据手算一遍,确认预期输出
假设 Scores 表里有下面这些数据:
| player_name | gender | day | score_points |
|---|---|---|---|
| Aron | M | 2020-01-01 | 17 |
| Alice | F | 2020-01-01 | 17 |
| Bao | M | 2020-01-01 | 8 |
| Jalen | M | 2020-01-07 | 25 |
| Frank | M | 2020-01-08 | 6 |
| Nikki | F | 2020-01-09 | 12 |
| Ken | M | 2020-01-09 | 11 |
| Bryan | M | 2020-01-09 | 7 |
| Vivian | F | 2020-01-10 | 11 |
