查询与聚合
order-by、group-by、having、sum/count/avg/min/max。
查询与聚合
这一课你将能做到
- 背出并运用 SQL 的逻辑执行顺序(FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT)
- 熟练写出 WHERE 行过滤 + GROUP BY 分组 + HAVING 筛组的报表查询,并解释为什么
WHERE COUNT(*) > 1是语法错误 - 区分
COUNT(*)、COUNT(列)、COUNT(DISTINCT 列)的行为差异,正确计算 AC 率这类比例指标
为什么要学
报表和统计是后端日常工作的一半:运营要看每日提交量、面试官要你算「通过率最高的用户」,这些全是 GROUP BY + HAVING 的组合拳。更关键的是,only_full_group_by 默认开启后,很多网上抄来的 SQL 在 8.0 直接报错——你必须理解分组语义才能改对,而不是随手加 ANY_VALUE() 糊弄过去。
人话:WHERE 是「发试卷之前先筛掉不参加的人」,HAVING 是「考完之后只看平均分及格的班」。一个是行级过滤,一个是组级过滤,时机不同。
先建立图像
SQL 的逻辑执行顺序(注意这不是实际执行方式,而是语义上的求值顺序):
FROM / JOIN → 确定数据源
WHERE → 逐行过滤(此时还没有"组")
GROUP BY → 把行折叠成组
HAVING → 对组过滤
SELECT → 计算输出列(聚合、别名在这里诞生)
ORDER BY → 排序(可用 SELECT 的别名)
LIMIT → 截取
为什么 WHERE 里不能用 SELECT 的别名、也不能用聚合函数?因为按顺序执行到 WHERE 时,SELECT 还没发生、组也还没形成。这一张图能解答你 80% 的报错。
跟着做
步骤 0:数据准备
USE sql_practice;
TRUNCATE TABLE cf_submission;
TRUNCATE TABLE cf_practice_record;
TRUNCATE TABLE cf_user;
TRUNCATE TABLE cf_problem;
-- 执行第 1 课步骤 4 的四段 INSERT,恢复到 6 用户 6 题 12 提交的初始状态
步骤 1:按难度统计提交量与 AC 数
SELECT
p.difficulty,
COUNT(*) AS total_sub,
SUM(s.status = 'accepted') AS ac_cnt
FROM cf_submission s
JOIN cf_problem p ON p.id = s.problem_id
GROUP BY p.difficulty;
预期结果:
| difficulty | total_sub | ac_cnt |
|---|---|---|
| easy | 6 | 3 |
| hard | 1 | 1 |
| medium | 5 | 3 |
步骤 2:WHERE 与 HAVING 的分工
目标:找出 AC 率不低于 60% 的用户。
SELECT
u.username,
COUNT(s.id) AS total_sub,
SUM(s.status = 'accepted') AS ac_cnt,
ROUND(SUM(s.status = 'accepted') / COUNT(s.id) * 100, 1) AS ac_rate
FROM cf_user u
JOIN cf_submission s ON s.user_id = u.id
GROUP BY u.id, u.username
HAVING ac_rate >= 60
ORDER BY ac_rate DESC, u.username;