SQL JOIN、GROUP BY 与聚合查询
串联 JOIN、GROUP BY 与聚合函数:先组合关系,再确定统计粒度,最后对每组计算,并处理 NULL、过滤阶段和行数放大。
#synthesis
#growing
#backend
#sql
#database
[!info] related notes
- 所属 MOC: 数据库 MOC
- 原子概念: SQL JOIN, SQL GROUP BY, SQL 聚合函数
- 过滤边界: SQL JOIN 中 ON 与 WHERE 的过滤边界, SQL HAVING 和 WHERE 的区别
- 性能入口: SQL 查询执行流程, 数据库索引
SQL JOIN、GROUP BY 与聚合查询
核心链路
很多业务查询都可以先按三步思考:
JOIN
→ 把需要的事实组合到同一结果粒度
GROUP BY
→ 明确要“按谁”统计
Aggregate
→ 对每一组算 COUNT / SUM / AVG / MIN / MAX
例如“所有用户及其订单数,包括 0 单用户”:
SELECT
u.id,
u.name,
COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o
ON o.user_id = u.id
GROUP BY u.id, u.name;
为什么顺序重要
先 JOIN
LEFT JOIN 保留所有 User,并把无订单用户的订单列补成 NULL。
再 GROUP BY
一个用户匹配多条订单后会出现多行,因此必须明确“按 User”形成统计组。
最后 COUNT
这里使用:
COUNT(o.id)
而不是 COUNT(*)。无订单用户虽然仍有一条 LEFT JOIN 结果行,但 o.id = NULL,所以 COUNT(o.id) = 0。
过滤到底放哪一层
逻辑心智模型:
FROM / JOIN
→ ON 控制匹配
→ WHERE 过滤参与分组的行
→ GROUP BY
→ Aggregate
→ HAVING 过滤聚合后的组
→ SELECT / ORDER BY
因此:
- “只匹配 published Article,但仍保留没有 published Article 的 User”——右表条件常放
ON; - “只统计 paid Order”——放
WHERE; - “只保留订单数 >= 3 的 User”——放
HAVING。
最大的正确性风险:粒度被 JOIN 放大
如果同时 JOIN 两个 1:N:
User
├─ 3 Orders
└─ 4 Tags
直接把两边一起 JOIN 可能产生 3 × 4 = 12 行,从而把 COUNT、SUM 重复放大。
所以复杂统计前应明确:
当前结果集“一行代表什么”?
必要时先各自聚合、用子查询/CTE,或拆成批量查询,而不是盲目在一个大 JOIN 上直接 SUM。
性能边界
JOIN、GROUP BY 本身不是“慢查询”的同义词。性能取决于:
- 表规模和过滤选择性;
- JOIN 键和过滤列是否有合适索引;
- 统计信息是否准确;
- 是否产生巨大中间结果;
- 优化器选择的 Join / Aggregate 算法。
需要判断真实代价时,应继续看执行计划,而不是只凭 SQL 语法猜性能。
最短记忆
JOIN → 表怎么组合
GROUP BY → 按什么粒度分组
Aggregate → 每组算什么
WHERE → 哪些行参与
HAVING → 哪些组留下