where筛行、having筛组:where在group by前执行,作用于原始行,禁用聚合函数;having在group by后执行,作用于分组结果,必须用聚合函数或分组字段,且通常需配合group by使用。

WHERE 筛的是行,HAVING 筛的是组
这是唯一需要刻进本能的区别。数据库执行时,WHERE在GROUP BY之前运行,它看到的是一条一条原始记录;HAVING在GROUP BY之后运行,它看到的是已经分好组、聚合值(比如COUNT(*)、SUM(price))都算出来的结果集。
所以不是“哪个更方便”,而是数据库根本不允许你颠倒顺序:WHERE里写COUNT(*) > 10会直接报错,因为此时COUNT(*)还没算出来;HAVING里写order_date > '2025-01-01'往往无效或报错,除非order_date在GROUP BY中——它根本不是“组”的属性。
WHERE 不能用聚合函数,HAVING 必须依赖 GROUP BY
WHERE只能引用原始表中的列或表达式,比如status = 'active'、price BETWEEN 100 AND 500;一旦出现SUM()、AVG()、COUNT(),语法就崩了。
HAVING必须配合GROUP BY才有明确语义(MySQL 允许不写GROUP BY的全表聚合查询,比如SELECT COUNT(*) FROM users HAVING COUNT(*) > 100,但这属于方言特例,不可移植,也容易掩盖逻辑错误)。
- ✅ 合法且推荐:
SELECT dept, COUNT(*) AS cnt FROM emp GROUP BY dept HAVING cnt > 3 - ❌ 避免:
SELECT COUNT(*) FROM users HAVING COUNT(*) > 100(没GROUP BY,意图模糊) - ⚠️ 注意:
HAVING可以引用SELECT中定义的聚合别名,如cnt,这是标准且安全的写法
混用 WHERE 和 HAVING 时,性能差出一个数量级
关键在数据缩减时机:WHERE能提前把大量无关行过滤掉,后续分组和聚合只处理剩下来的子集;HAVING则必须先把所有数据分完组、算完聚合值,再扔掉不满足条件的组。
比如查“2025年下单且总金额超5万的客户”:
- ✅ 高效:
WHERE order_year = 2025先筛掉90%历史订单,再GROUP BY customer_id,最后HAVING SUM(amount) > 50000 - ❌ 低效:把
order_year = 2025塞进HAVING,数据库得对全部年份做分组求和,内存和IO压力陡增 - ? 查执行计划时重点看
rows_examined:优化后该值应明显下降;若没变,说明WHERE条件被误放到了HAVING
最容易被忽略的点:错放条件不会报错,但性能已悄然崩坏
把本该写在WHERE里的单行条件(如时间、状态、ID范围)挪到HAVING,查询常常能跑通——尤其当字段也在GROUP BY里时——但代价是数据库多做了大量无意义的分组和聚合计算。
这种错误不显眼:结果可能正确,日志没报错,监控看不出异常,直到某天数据量翻倍、查询变慢三倍,才意识到是WHERE和HAVING的位置搞反了。











