WHERE过滤行,HAVING过滤组;WHERE在GROUP BY前执行,不可用聚合函数;HAVING在GROUP BY后执行,必须配合分组和聚合函数使用,且仅能引用分组字段或聚合结果。

WHERE 过滤行,HAVING 过滤组
根本区别不在语法,而在执行时机:WHERE 在 GROUP BY 和聚合计算之前运行,只看到原始表的每一行;HAVING 在分组和所有聚合函数(如 COUNT(*)、AVG())算完之后才执行,只看到每组一条“汇总结果”。所以你写 WHERE COUNT(*) > 5,MySQL 直接报 Invalid use of group function——这个值此时根本不存在。
HAVING 里用非分组字段会报错
比如 SELECT user_id, COUNT(*) FROM logs GROUP BY user_id HAVING action = 'login',这条语句非法。因为 action 既没出现在 GROUP BY 中,也没被聚合函数包裹,分组后它已经“消失”了。MySQL 8.0+ 开启 ONLY_FULL_GROUP_BY(默认)时,连 SELECT user_id, name, COUNT(*) FROM users GROUP BY user_id 都会拒绝——name 不确定取哪一行,更别说在 HAVING 里拿来比了。
性能差异不是理论,是磁盘 IO 和内存开销
把本该放 WHERE 的条件挪到 HAVING,等于让数据库先读全表、建临时分组结构、算完所有聚合,最后才扔掉 99% 的结果。常见坑包括:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
WHERE status = 'paid'写成HAVING status = 'paid'→ 全表扫描不可避免,哪怕status有索引 -
WHERE created_at >= '2025-01-01'放进 HAVING → 时间范围过滤失效,旧数据全参与分组 - 视图里定义含
HAVING,外层加WHERE id = 123也下推不了,EXPLAIN 看到type: ALL和Using temporary; Using filesort
别名能用在 HAVING,但不能解决逻辑错位
SELECT department, AVG(salary) AS avg_sal FROM emp GROUP BY department HAVING avg_sal > 15000 是合法的,因为别名在 HAVING 阶段已存在。但这不意味着你可以绕过 WHERE 的职责。比如你想查“上海地区订单数超 5 的用户”,正确写法是:
SELECT user_id, COUNT(*) FROM orders WHERE region = 'Shanghai' GROUP BY user_id HAVING COUNT(*) > 5;
而不是:
SELECT user_id, COUNT(*) FROM orders GROUP BY user_id HAVING region = 'Shanghai' AND COUNT(*) > 5;
后者不仅报错,还暴露了一个关键事实:HAVING 无法替代 WHERE 的根本原因,是它压根不处理“单行原始数据”的筛选逻辑——那不是它的工作范围。

















