where 先过滤,having 后过滤;where 在 group by 前作用于原始行,having 在分组后作用于聚合结果,且仅能引用 select 列、group by 字段或聚合函数。
where 和 having 到底谁先过滤?
分组前用 where,分组后用 having——这是最常被记反的逻辑。如果你在 having 里写了没出现在 select 或 group by 中的非聚合字段(比如 having user_name = 'alice'),mysql 会报 unknown column 'user_name' in 'having clause',postgresql 更直接拒绝执行。
-
WHERE在GROUP BY之前生效,只能引用原始表字段 -
HAVING在分组完成之后执行,只能引用SELECT中的列、GROUP BY字段或聚合函数(如COUNT()、AVG()) - 想筛“订单数超过 5 的用户”,必须写
HAVING COUNT(*) > 5;写成WHERE COUNT(*) > 5语法错误
GROUP BY 字段不全导致 HAVING 失效?
MySQL 5.7 默认开启 sql_mode=ONLY_FULL_GROUP_BY,这时如果 SELECT 里有非分组字段(如 SELECT user_id, name, COUNT(*) 但只 GROUP BY user_id),即使没用到 HAVING 也会报错。而一旦你关掉这个模式,HAVING 可能看似正常,实则返回不可靠结果——比如 name 值是任意一条记录的,不是分组代表值。
- 确保
SELECT中所有非聚合字段都出现在GROUP BY列表中 - 别依赖 MySQL 的宽松模式来“绕过”问题,PostgreSQL 和新版本 MySQL 都严格校验
- 如果真要取某个分组内的特定值(如最新时间的
status),得用窗口函数或子查询,不能靠HAVING
HAVING 里用聚合函数要注意 NULL 和性能
HAVING COUNT(col) > 1 和 HAVING COUNT(*) > 1 行为不同:前者忽略 col 为 NULL 的行,后者统计所有行。另外,HAVING 无法利用索引加速,它是在内存中对分组结果集做二次扫描,数据量大时容易拖慢查询。
- 优先用
WHERE过滤掉大量无关行,减少分组输入量——比如先WHERE status = 'active'再分组 - 避免在
HAVING中用复杂表达式,如HAVING AVG(price * tax_rate) > 100,尽量提前计算或建生成列 -
HAVING条件中的聚合函数不会触发隐式类型转换,但若字段类型混用(如字符串和数字比较),仍可能出意外结果
想在 HAVING 后再排序或分页?顺序不能错
ORDER BY 和 LIMIT 必须写在 HAVING 之后,否则语法报错。但更要小心的是:如果 ORDER BY 引用了未出现在 SELECT 中的字段(比如只选了 user_id 和 COUNT(*),却 ORDER BY created_at),多数数据库会拒绝执行。
- 排序字段必须是
SELECT列、GROUP BY列或聚合结果(如ORDER BY COUNT(*) DESC) -
LIMIT对分组后的结果生效,不是对原始行数限制——LIMIT 10是返回最多 10 个分组,不是 10 行原始数据 - 需要分页时,
OFFSET在大数据集上性能差,考虑用游标方式(如记录上一页最大COUNT(*)值)
真正麻烦的从来不是写对 HAVING,而是搞清它在哪一层生效、依赖哪些上下文、以及当它和索引、NULL、模式设置撞在一起时,哪条路会突然走不通。










