MySQL 8.0+ 要求 GROUP BY 必须包含 SELECT 中所有非聚合字段,否则报错;WHERE 用于行过滤,HAVING 用于分组后过滤;ORDER BY 推荐用别名排序;多字段分组时 NULL 被视为相同值但组合不同。

GROUP BY 后必须出现 SELECT 中的所有非聚合字段
MySQL 8.0+ 默认启用 sql_mode=ONLY_FULL_GROUP_BY,这意味着如果 SELECT 列表里有非聚合字段(比如 name、status),它们必须显式出现在 GROUP BY 子句中,否则报错:Expression #1 of SELECT list is not in GROUP BY clause。
常见错误写法:
SELECT id, name, COUNT(*) FROM users GROUP BY status;
这在 MySQL 5.7 之前可能“侥幸”通过,但结果不可靠——id 和 name 取的是哪一行的值?MySQL 不保证。8.0+ 直接拒绝执行。
- 正确做法:把所有非聚合列都放进
GROUP BY,例如GROUP BY status, name - 或改用聚合函数包裹,如
MAX(id)、ANY_VALUE(name)(需确认业务逻辑是否允许) - 不建议临时关掉
ONLY_FULL_GROUP_BY,它是为了防止隐式歧义而设的
WHERE 和 HAVING 的分工不能颠倒
WHERE 过滤行,HAVING 过滤分组,这是关键区别。很多人误把条件全塞进 HAVING,导致性能下降甚至逻辑错误。
比如查“每种状态的用户数超过 5 人的活跃用户”:
SELECT status, COUNT(*) AS cnt FROM users WHERE is_active = 1 GROUP BY status HAVING cnt > 5;
-
is_active = 1必须放WHERE:提前过滤,减少参与分组的数据量 -
cnt > 5必须放HAVING:因为cnt是分组后才计算出的别名,WHERE看不见它 - 若把
cnt > 5错写成WHERE COUNT(*) > 5,会直接报错:Invalid use of group function
ORDER BY 中引用聚合结果要小心别名和字段位置
GROUP BY 查询中,ORDER BY 可以用列别名、序号或表达式,但不同 MySQL 版本行为略有差异。
例如:
SELECT status, COUNT(*) AS total FROM users GROUP BY status ORDER BY total DESC;
- 用别名
total排序是安全且可读的,推荐 - 用位置序号如
ORDER BY 2虽然合法,但一旦调整SELECT列顺序就容易出错 - 避免在
ORDER BY中重复写聚合表达式,比如ORDER BY COUNT(*),虽然可行,但冗余且难维护 - 注意:MySQL 允许
ORDER BY引用未出现在SELECT中的字段(只要在GROUP BY里),但这种写法依赖隐式分组语义,易引发理解偏差
多字段分组时注意 NULL 值的分组行为
当 GROUP BY 包含多个字段,且其中某些值为 NULL,MySQL 把所有 NULL 视为“相同”,归入同一组。这和 WHERE col = NULL 永远为 false 不同,但容易被忽略。
例如:
SELECT dept, level, COUNT(*) FROM employees GROUP BY dept, level;
- 如果
dept或level有NULL,它们各自形成独立的NULL组(即(NULL, 'senior')、('tech', NULL)、(NULL, NULL)是三个不同组) - 但单个字段为
NULL的所有行,会全部聚到该字段对应的那个NULL组里 - 若想排除
NULL再分组,得加WHERE dept IS NOT NULL AND level IS NOT NULL - 用
COALESCE(dept, 'unknown')替换NULL是常见补救手段,但要注意它改变了原始分组语义
GROUP BY 时,最常卡住的地方不是语法,而是没想清楚“我要按什么维度聚合”和“这个维度的空值代表什么”。这两个问题没理清,后面加再多 HAVING 或 ORDER BY 都只是在掩盖逻辑漏洞。










