WHERE在聚合前筛选数据行,HAVING在聚合后过滤结果;WHERE可利用索引加速,HAVING只能遍历临时结果集;二者须按WHERE→GROUP BY→HAVING顺序使用,分工明确不可颠倒。

WHERE在聚合前就筛掉数据行
WHERE子句在SQL执行链路中位于FROM之后、GROUP BY和所有聚合函数(如COUNT()、SUM())之前。这意味着数据库引擎还没开始分组、排序、计算总和或计数,就已经把不满足条件的原始行丢弃了。数据量越小,后续步骤的CPU、内存和I/O开销就越低。
HAVING必须等聚合完成才能工作
HAVING子句的执行时机在GROUP BY和所有聚合计算之后。它面对的不是原始表行,而是已经分好组、并算出每个组的AVG(salary)或COUNT(*)的结果集。哪怕你只想保留2个组,数据库也得先把全部10万行分到500个组里、每个组都算一遍聚合值,最后才用HAVING挑出那2个——中间99%的计算白做了。
- 常见错误:把本该写在WHERE里的条件(比如
status = 'active')挪到HAVING里,导致全表扫描+全量分组 - 性能差异可能达数量级:对千万级订单表查“2023年订单数>5的客户”,WHERE先过滤年份能减少80%以上行数;若用HAVING硬扛,分组耗时可能从200ms涨到3s+
索引只能加速WHERE,对HAVING无效
数据库可以利用status字段上的索引快速定位满足WHERE status = 'active'的行,但无法为HAVING COUNT(*) > 5建索引——因为COUNT是运行时结果,不是存储在索引B+树里的列值。没有索引支持的HAVING过滤,只能靠扫描聚合后的临时结果集,本质是全表扫描的二次开销。
- WHERE中可用的优化手段:复合索引、范围查询下推、IN列表转哈希查找
- HAVING中能做的只有结果集遍历,连ORDER BY都得等它跑完才能接上
组合使用才是常态,但顺序不能错
实际查询中WHERE和HAVING经常共存,关键在于分工:WHERE管“哪些原始行值得参与计算”,HAVING管“哪些计算结果值得返回”。比如查“2023年入职且平均薪资超15k的部门”:WHERE hire_year = 2023必须在前,HAVING AVG(salary) > 15000必须在后。把后者提前到WHERE里会直接报错,把前者拖到HAVING里则失去索引和早期剪枝能力。
最容易被忽略的是:HAVING里能引用的列,仅限于GROUP BY中的列名或聚合表达式,连SELECT列表里别名写的avg_sal在多数数据库里也不能直接用——得重写成AVG(salary)。










