
WHERE 和 HAVING 到底谁先过滤
WHERE 在分组前筛数据,HAVING 在分组后筛分组结果——这是最常被搞反的逻辑。比如想查「订单数超过 5 的用户」,如果写成 WHERE COUNT(*) > 5,MySQL 直接报错:Invalid use of group function,因为 COUNT(*) 还没算出来,WHERE 根本看不到它。
正确顺序是:WHERE → GROUP BY → HAVING。WHERE 可以用原始字段(如 user_id、created_at),HAVING 只能用聚合结果(COUNT(*)、AVG(price))或 SELECT 中定义的别名(前提是 MySQL 版本 ≥ 5.7.5 且 SQL 模式不包含 ONLY_FULL_GROUP_BY)。
HAVING 必须跟在 GROUP BY 后面吗
是的,语法上 HAVING 不能单独出现,必须紧接在 GROUP BY 之后。但有个例外:不写 GROUP BY 时,整张表会被当作一个分组,此时 HAVING 仍可工作——比如查全表平均价格是否超限:SELECT AVG(price) FROM products HAVING AVG(price) > 100。
- 没写
GROUP BY却用了HAVING,MySQL 不报错,但语义容易误导,建议显式写GROUP BY ()或改用子查询 - 在视图或子查询里用 HAVING,要注意外层是否还允许聚合函数出现在 WHERE 中
- 某些 ORM(如 Laravel 的 Query Builder)生成 HAVING 时会自动补 GROUP BY,但原生 SQL 写漏了就会报语法错误:
Syntax error near 'HAVING'
用别名在 HAVING 里安全吗
在 SELECT 中给聚合结果起别名(如 SELECT COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING cnt > 5),这个 cnt 能不能在 HAVING 里用,取决于 MySQL 版本和 SQL 模式:
- MySQL 5.7.5+ 且未启用
ONLY_FULL_GROUP_BY:可以,cnt被识别为列别名 - 启用
ONLY_FULL_GROUP_BY(默认开启于 8.0+):部分情况会报错,尤其当 SELECT 中有非聚合字段又没出现在 GROUP BY 里时 - 最稳妥写法:HAVING 里直接写聚合表达式,比如用
HAVING COUNT(*) > 5而不是依赖别名
别名写法看着干净,但换环境迁移或升级 MySQL 版本后容易突然失效。
HAVING 性能比 WHERE 差很多吗
差,而且差得明确:WHERE 能用索引快速排除行,HAVING 是对内存中已分组的结果再过滤,无法走索引。比如 SELECT user_id, COUNT(*) FROM orders WHERE status = 'paid' GROUP BY user_id HAVING COUNT(*) > 10,前面的 WHERE status = 'paid' 能用索引加速,后面的 HAVING 只能扫完所有分组再逐个判断。
- 优先把能下推的条件写进 WHERE,哪怕多写一遍逻辑(比如时间范围、状态码)
- HAVING 里避免嵌套函数,像
HAVING DATE_FORMAT(MAX(created_at), '%Y-%m') = '2024-01'会让优化器放弃使用 MAX 上的索引 - 如果 HAVING 过滤掉大量分组,考虑是否该提前在 WHERE 或 JOIN 条件里收缩数据集
真正难处理的是那种必须分组后才能判定的逻辑——比如「用户最近三笔订单总价超 500」,这种绕不开 HAVING,也躲不开性能代价。









