sql分组聚合核心在于理解group by先切分再独立计算的逻辑,以及where(分组前过滤)与having(分组后筛选)的本质区别;所有非聚合字段必须出现在group by中,NULL值会被归为同一组。

SQL分组聚合是面试高频考点,核心不在语法本身,而在对 GROUP BY 执行逻辑 和 HAVING 与 WHERE 区别 的准确理解。写错往往不是不会,而是没想清“数据分组前/后”这个关键分界点。
GROUP BY 的本质:先切分,再独立计算
GROUP BY 不是“按某列排序+合并”,而是把表按指定列的**所有唯一组合**切成若干子集,每个子集独立执行后续聚合(SUM、count、AVG等)。只要列出现在 select 中且未被聚合函数包裹,就必须出现在 GROUP BY 子句中(mysql 5.7+ 严格模式默认开启)。
- 错误写法:
SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id→ name 可能有多个值,数据库无法确定返回哪一个 - 正确写法:
SELECT user_id, MAX(name), COUNT(*) FROM orders GROUP BY user_id或SELECT user_id, name, COUNT(*) FROM orders GROUP BY user_id, name - 小技巧:用
GROUP BY 1,2可简写位置序号(如 postgresql、MySQL 支持),但不推荐在面试中使用,易失可读性
HAVING 是分组后的筛选,WHERE 是分组前的筛选
WHERE 在分组前过滤行(作用于原始数据),HAVING 在分组后过滤组(作用于聚合结果)。这是最常被混淆的点。
- 查“订单数 ≥ 3 的用户” → 必须用 HAVING:
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING COUNT(*) >= 3 - 查“2024 年的订单中,订单数 ≥ 3 的用户” → WHERE 先筛年份,HAVING 再筛组:
SELECT user_id, COUNT(*) FROM orders WHERE order_date >= '2024-01-01' GROUP BY user_id HAVING COUNT(*) >= 3 - 不能写成
WHERE COUNT(*) >= 3—— 聚合函数在 WHERE 阶段尚未计算,语法直接报错
常见陷阱题型与应对思路
面试官爱考“绕弯但逻辑清晰”的题,本质是检验你能否拆解步骤:
- 找每个部门工资最高的员工(含姓名):不能只靠 GROUP BY dept + MAX(salary),因为 MAX() 只返回值,不带关联行。需用窗口函数(
ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC))或自连接(子查询先得 max,再关联原表) - 统计连续登录天数 ≥ 3 的用户:核心是将日期转为“日期 – ROW_NUMBER()”构造连续组(即“间隙识别法”),再 GROUP BY 用户+该组标识,最后 HAVING COUNT(*) >= 3
- 分组后取每组最新一条记录:同样避免仅用 MAX(create_time),要用子查询或窗口函数获取完整行
GROUP BY 与 NULL 的关系要特别注意
NULL 在 GROUP BY 中被视为相同值——所有 NULL 会归入同一组。这容易导致统计偏差,尤其在业务字段允许为空时。
- 例如:
SELECT category, COUNT(*) FROM products GROUP BY category,category 为 NULL 的所有行会被聚成一组,显示为(NULL, 5) - 若想排除空值,加 WHERE:
WHERE category IS NOT NULL - 若想单独标记空值组,可用
CASE WHEN category IS NULL THEN 'UNKNOWN' ELSE category END转换后再分组