having必须紧接group by且仅能使用分组字段或聚合函数,否则报错;where在分组前筛选原始数据,having在分组后筛选统计结果;mysql 5.7+默认启用only_full_group_by,要求select与group by严格对齐。

HAVING 必须紧接 GROUP BY,且只能用分组字段或聚合函数
单独写 HAVING 会报错,比如 HAVING COUNT(*) > 5 放在没 GROUP BY 的语句里,MySQL 直接抛出 SQLSTATE[HY000]: General error: 1140。因为 HAVING 的作用对象是“每组聚合完之后的那行结果”,没分组就没有这个上下文。
能出现在 HAVING 条件里的只有两类东西:
- 出现在
GROUP BY子句中的列,比如department、user_id - 聚合表达式,比如
COUNT(*)、AVG(salary)、SUM(amount) - 不能用原始行字段,例如
HAVING salary > 8000——salary既没进GROUP BY,也没被聚合,这行数据在HAVING阶段根本不存在
SELECT 列表和 GROUP BY 必须对齐,否则触发 ERROR 1055
MySQL 5.7+ 默认开启 ONLY_FULL_GROUP_BY,这是硬性限制,不是警告。比如这句会报错:
SELECT name, department, COUNT(*) FROM employees GROUP BY department;
因为 name 既不在 GROUP BY 里,也没套 MAX(name) 或 ANY_VALUE(name) 这类聚合,数据库无法确定该取哪一行的 name。
正确写法只有两种:
- 把
name加进GROUP BY(适合你真要按人分组) - 或者改用聚合:比如
MAX(name)、GROUP_CONCAT(name),明确告诉数据库你要什么 - 关掉
ONLY_FULL_GROUP_BY是掩耳盗铃——查询可能跑通,但结果不可靠,换到 PostgreSQL 或 SQL Server 就直接失败
WHERE 和 HAVING 的执行顺序决定性能和逻辑边界
SQL 实际执行顺序是:FROM → WHERE → GROUP BY → 聚合计算 → HAVING → SELECT → ORDER BY。这意味着:
-
WHERE在分组前运行,能大幅减少参与分组的数据量,尤其对千万级订单表,加WHERE order_date >= '2026-01-01'可能让分组耗时从 8 秒降到 0.3 秒 -
HAVING是在所有组都算完之后才扫一遍结果集,HAVING COUNT(*) > 100不会跳过任何组的计数计算 - 想筛“部门里平均工资超 12000 的活跃员工”,必须写成:
WHERE status = 'active' GROUP BY department HAVING AVG(salary) > 12000;如果把status = 'active'塞进HAVING,语法非法,且逻辑错乱
NULL 和空分组容易导致 HAVING 行为不符合直觉
聚合结果为 NULL 时,HAVING 条件判断会返回 UNKNOWN,整组被排除。例如:
-
HAVING AVG(salary) > 10000:某部门所有salary都是NULL,AVG()返回NULL,NULL > 10000判为false,该组消失 -
HAVING COUNT(*) > 0:哪怕所有字段都是NULL,只要有一行,COUNT(*)就是 1,这组会被保留 - MySQL 和 PostgreSQL 对
NULL的默认处理一致,但若启用了transform_null_equals = on(PostgreSQL),NULL = NULL可能变true,间接影响HAVING中带IS NULL的逻辑
真正难的不是语法,是区分“我要筛原始数据”还是“我要筛统计结果”——写错一个字,要么报错,要么查出错数据,而且很难一眼看出来。










