6. 聚合与分组
这是什么:聚合 = 把很多行算成一个数(总数、总和、平均);GROUP BY = 先按某列分组,再对每组聚合。
为什么用:「博客有多少篇文章」「哪位作者平均阅读量最高」——凡是带「每、多少、平均」的问题,都是聚合查询。这章的 SQL 你在 MySQL 教程练过一遍,这里逐字不改也能跑。
如何用:还是 user + article 两张表,统计跑起来,顺便验证「学的 SQL 两边通用」。
说明:本章沿用前几章改完之后的数据——第 4 章删掉了《MySQL 练手记》、把第 1 篇阅读量加到了 130,所以现在库里只有 2 篇文章。
6.1 五个聚合函数
| 函数 | 算什么 | 示例问题 |
|---|---|---|
| COUNT | 行数 | 有多少篇文章 |
| SUM | 求和 | 总阅读量 |
| AVG | 平均 | 平均阅读量 |
| MAX / MIN | 最大 / 最小 | 最火 / 最凉的文章 |
先来两个单组统计:
SELECT COUNT(*) FROM article;
SELECT MAX(views) FROM article;
预期输出:
2
130
6.2 GROUP BY:按作者统计
想知道每位作者的文章数和平均阅读量,就按 user_id 分组:
SELECT user_id,
COUNT(*) AS 文章数,
AVG(views) AS 平均阅读
FROM article
GROUP BY user_id;
预期输出:
1|2|107.5
现在有文章的只剩 frog(两篇:130 和 85),所以只有一行。
6.3 带上作者名:先 JOIN 再分组
分组键换成用户表的用户名,顺带把没发过文章的作者也统计出来(LEFT JOIN),这就是 MySQL 章同款练习:
SELECT u.username,
COUNT(a.id) AS 文章数,
IFNULL(AVG(a.views), 0) AS 平均阅读
FROM user u
LEFT JOIN article a ON a.user_id = u.id
GROUP BY u.id
ORDER BY 文章数 DESC;
预期输出(panda 没发过文章,被兜底成 0):
frog|2|107.5
panda|0|0
两个细节:COUNT(a.id) 数的是文章行数(没文章就是 0),而 COUNT(*) 会把 LEFT JOIN 出的空行也数进去——统计「每组的文章数」要用列名;IFNULL(..., 0) 把无文章的 NULL 平均值兜底成 0。
6.4 HAVING:对分组结果再筛选
WHERE 筛的是原始行,HAVING 筛的是分组后的统计值。只要文章数 ≥ 2 的作者:
SELECT u.username, COUNT(a.id) AS 文章数
FROM user u
LEFT JOIN article a ON a.user_id = u.id
GROUP BY u.id
HAVING 文章数 >= 2;
预期输出:
frog|2
口诀:WHERE 管行,HAVING 管组。
6.5 动手改
- 统计全站总阅读量与文章总数(一条 SELECT,两个聚合)
- 找出平均阅读量高于 90 的作者(JOIN + GROUP BY + HAVING)
- 按月统计文章数(提示:
substr(created_at, 1, 7)当分组键)
6.6 小结
- 聚合五件套 COUNT / SUM / AVG / MAX / MIN
- GROUP BY 分组统计,HAVING 筛组;WHERE 管行,HAVING 管组
- 同一条统计 SQL 在 MySQL 和 SQLite 里结果一致——学一次两边用
下一章:多表查询与外键 🐸