MySQL严格模式下GROUP BY后SELECT字段必须为分组键或聚合函数,否则报错;多字段分组需注意顺序以匹配索引;HAVING过滤分组结果,WHERE过滤原始行;NULL值会被归为同一组。

GROUP BY 后 SELECT 的字段必须是分组键或聚合函数
MySQL 严格模式下,SELECT 列表里出现的非聚合字段若没在 GROUP BY 中声明,会直接报错:Expression #1 of SELECT list is not in GROUP BY clause。这不是 bug,是 SQL 标准行为,MySQL 5.7+ 默认开启 sql_mode=ONLY_FULL_GROUP_BY。
常见错误写法:SELECT id, name, COUNT(*) FROM users GROUP BY city; —— id 和 name 没参与分组,也不带聚合,必然失败。
- 正确做法:只选分组字段或套聚合函数,比如
SELECT city, COUNT(*), MAX(age) FROM users GROUP BY city; - 临时绕过(不推荐):执行
SET sql_mode=(SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY',''));,但上线环境禁用 - 想查每个城市的“某条代表记录”?得用子查询或窗口函数,不能靠 GROUP BY 直出
GROUP BY 多字段时顺序和语义很重要
写成 GROUP BY a, b 和 GROUP BY b, a 结果一样,但可读性和索引利用差别很大。MySQL 会按字段顺序生成分组哈希或排序,也影响联合索引是否生效。
比如有联合索引 (status, created_at),而你写 GROUP BY created_at, status,这个索引大概率被忽略——因为最左前缀不匹配。
- 优先让高区分度字段靠前,减少中间分组数量(如
GROUP BY user_id, action_type比反过来更高效) - 如果后续要加
ORDER BY,尽量让GROUP BY字段顺序与之对齐,避免额外排序 -
GROUP BY (a,b)等价于GROUP BY a,b,括号无实际作用,别误以为能“分组嵌套”
HAVING 和 WHERE 的分工必须分清
WHERE 过滤行,HAVING 过滤分组。很多人把条件写错位置,导致结果偏差或性能暴跌。
典型错误:SELECT city, COUNT(*) FROM users WHERE COUNT(*) > 10 GROUP BY city; —— COUNT(*) 在 WHERE 阶段还不存在,语法直接报错。
- 过滤原始数据用
WHERE:比如WHERE status = 'active' - 过滤聚合结果用
HAVING:比如HAVING COUNT(*) >= 5 -
HAVING可以引用SELECT中的别名(如SELECT city c, COUNT(*) cnt GROUP BY city HAVING cnt > 5),但WHERE不行
NULL 值在 GROUP BY 中会被当作同一组
所有 NULL 值在分组时视为相等,会挤进同一个分组。这常导致统计偏差,尤其当业务字段允许为空又参与分组时。
例如 SELECT tag, COUNT(*) FROM articles GROUP BY tag;,所有 tag IS NULL 的记录会归为一组,显示为 NULL,但你可能根本没意识到这部分数据量有多大。
- 显式分离空值:
GROUP BY IF(tag IS NULL, 'unknown', tag) - 或者提前过滤:
WHERE tag IS NOT NULL,再分组 - 注意:不同 MySQL 版本对
NULL排序位置略有差异(默认排最前),但分组行为一致
GROUP BY 看似简单,但字段合法性、NULL 处理、索引配合、HAVING 时机这几个点,一个没踩准就容易出错或慢得离谱。尤其是线上查慢查询日志时,十次有六次是 GROUP BY 没走索引或写了无效字段。


















