mysql 8.0.13+ 启用 only_full_group_by 后,select 中非聚合字段必须出现在 group by 中或用 any_value() 等函数包裹,否则报错;having 仅能过滤分组后聚合结果,where 才过滤原始行;group by 本身不支持取每组特定行,需用窗口函数或子查询实现。

MySQL 8.0.13+ 默认启用 ONLY_FULL_GROUP_BY 模式,这意味着:只要 SELECT 里出现非聚合字段(比如 name、status),它就必须明确写在 GROUP BY 子句中,否则直接报错 Expression #1 of SELECT list is not in GROUP BY clause。
GROUP BY 字段必须和 SELECT 非聚合列完全一致
这不是“建议”,而是严格模式下的硬性要求。常见错误是只按一个维度分组,却试图取多个未聚合的字段值:
- ❌ 错误写法:
SELECT id, name, COUNT(*) FROM users GROUP BY status;——id和name既没聚合也没出现在GROUP BY中 - ✅ 正确思路一(推荐):只保留分组键 + 聚合结果,例如
SELECT status, COUNT(*), AVG(age) FROM users GROUP BY status; - ✅ 正确思路二(需明确语义):真要取某组内任意一条记录的
name,用ANY_VALUE(name)显式声明,例如SELECT status, ANY_VALUE(name), COUNT(*) FROM users GROUP BY status;
注意:ANY_VALUE() 不是“随机取”,而是告诉 MySQL:“我知道这个值不唯一,但我接受它来自该组任意一行”。它比关掉 ONLY_FULL_GROUP_BY 更安全、更可维护。
HAVING 必须作用于聚合结果,不能替代 WHERE
HAVING 是对分组后的结果集过滤,不是对原始行过滤。混淆两者会导致语法错误或逻辑错误:
- ✅ 正确:
SELECT department, COUNT(*) c FROM employees GROUP BY department HAVING c > 5;—— 先分组,再筛出人数超 5 的部门 - ❌ 错误:
SELECT department, COUNT(*) c FROM employees WHERE c > 5 GROUP BY department;——c是聚合别名,WHERE阶段根本不存在 - ⚠️ 性能提示:
WHERE可走索引,HAVING是内存扫描。尽量把能提前过滤的条件(如WHERE status = 'active')写在GROUP BY前
想取每组最新/最大/最小的一整行?GROUP BY 本身做不到
GROUP BY 只负责聚合计算,不负责“选行”。以下写法看似可行,实则危险:
SELECT user_id, MAX(create_time), content FROM logs GROUP BY user_id;
这里 content 的值是不确定的——它来自该组中某条随机记录,不是与 MAX(create_time) 对应的那一行。
- ✅ MySQL 8.0+ 推荐用窗口函数:
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time DESC),外层WHERE rn = 1 - ✅ 兼容老版本可用自连接或关联子查询,但要注意性能:数据量大时可能慢几倍
- ❌ 别用
GROUP_CONCAT(content ORDER BY create_time DESC SEPARATOR '|||')再截取——字段含分隔符时会崩,且无法还原原始类型(如 JSON、时间戳)
多列分组顺序影响结果结构和 NULL 处理
GROUP BY a, b 和 GROUP BY b, a 在语法上等价,但实际输出的分组层次、排序倾向、以及 WITH ROLLUP 的小计位置都不同。更关键的是:
-
NULL值会被单独归为一组,且NULL = NULL成立 —— 所以所有NULL会聚在一起;但如果你用COALESCE(a, 'unknown')包裹后再分组,就可控了 - 多列分组时,如果某列允许
NULL,而你又依赖HAVING筛选,记得测试NULL组是否被意外包含或排除
最易被忽略的点:分组字段的表达式(比如 DATE(created_at))必须和 SELECT 中的表达式完全一致,否则即使逻辑相同,MySQL 也可能拒绝执行。











