GROUP BY必须与聚合函数配合使用,非聚合字段须出现在GROUP BY子句中或包裹于聚合函数内;HAVING用于分组后过滤,WHERE用于分组前过滤;多字段分组按顺序影响语义,NULL值在分组中被视为同一组。

GROUP BY 必须和聚合函数一起用,单独写会报错
直接写 SELECT name FROM users GROUP BY city 在大多数数据库(如 MySQL 8.0+、PostgreSQL、SQL Server)中会报错,提示 column "name" must appear in the GROUP BY clause or be used in an aggregate function。这是因为分组后每组可能有多个 name,数据库不知道该取哪一个。
正确做法是:所有非聚合字段必须出现在 GROUP BY 子句中,或包裹在聚合函数里:
-
SELECT city, COUNT(*) FROM users GROUP BY city✅ -
SELECT city, MAX(age), AVG(salary) FROM users GROUP BY city✅ -
SELECT city, name FROM users GROUP BY city❌(MySQL 5.7 兼容模式下可能“侥幸”通过,但结果不可靠)
HAVING 用来过滤分组结果,WHERE 不能替代它
WHERE 在分组前过滤行,HAVING 在分组后过滤组。想查“人数超过 5 的城市”,不能写 WHERE COUNT(*) > 5——因为 COUNT(*) 此时还没算出来,会直接报错 aggregate function calls cannot be used in WHERE。
实操要点:
- 先用
WHERE排除非目标数据(比如WHERE status = 'active'),减少分组基数 - 再用
HAVING筛选聚合结果(比如HAVING COUNT(*) >= 5) -
HAVING后可跟别名(如HAVING user_cnt > 5),但部分数据库(如 MySQL)要求别名必须加反引号或直接写表达式
示例:
SELECT city, COUNT(*) AS user_cnt FROM users WHERE deleted = 0 GROUP BY city HAVING COUNT(*) > 5;
多字段分组要注意顺序和组合语义
GROUP BY a, b 和 GROUP BY b, a 结果一样,但语义不同:前者按 a 主分组、b 次分组,适合后续加 ORDER BY a, b;后者则相反。更关键的是,组合分组会产生笛卡尔式分组桶。
常见陷阱:
- 误以为
GROUP BY gender, city能自动补全缺失组合(比如某城市没有女性用户,就不会出现该行)→ 实际只会返回真实存在的组合 - 在报表中需要“所有城市 × 所有性别”的完整矩阵时,得用
CROSS JOIN预生成再LEFT JOIN统计,不能只靠GROUP BY - 字段类型要一致:如果
city是VARCHAR但含空格或大小写混用,'Beijing '和'beijing'会被当成两个组
NULL 值在 GROUP BY 中被当作同一组处理
这是容易被忽略的行为:所有 NULL 值在分组时视为相等,会归入同一个组。例如 SELECT department, COUNT(*) FROM staff GROUP BY department,所有 department IS NULL 的记录会合并成一行,department 字段显示为 NULL。
如果你需要区分“明确为空”和“未录入”,得提前处理:
- 用
CASE WHEN department IS NULL THEN 'UNKNOWN' ELSE department END转换后再分组 - 或者用
COALESCE(department, 'MISSING')替换 NULL - 注意:
GROUP BY不会把NULL和空字符串''当作同一组,它们是不同值
这个行为在做数据质量检查时特别关键——你看到的“NULL 组数量”可能掩盖了大量脏数据混合问题。

















