
因为 HAVING 必须等分组和聚合计算完成之后才能工作,而 WHERE 在那之前就已结束——这不是设计偏好,是 SQL 执行引擎的硬性流程决定的。
SQL 实际执行顺序不可跳过
数据库不是按你写的顺序执行 SQL,而是严格遵循:FROM → WHERE → GROUP BY → 聚合函数(如 COUNT()、SUM())→ HAVING → SELECT → ORDER BY。这个链条里没有“插队”可能。
-
WHERE阶段只能看到原始表里的字段,比如order_date或status,此时连“每个客户的订单数”都还没算出来 -
GROUP BY一执行,数据才被切分成若干组;紧接着聚合函数才逐组计算出COUNT(*)、AVG(price)这类值 -
HAVING是唯一能“看见”这些聚合结果的地方,它筛选的是组,不是行
写错位置会直接报语法错误
常见错误就是把聚合条件塞进 WHERE,比如:
SELECT customer_id, COUNT(*) FROM orders WHERE COUNT(*) > 10 GROUP BY customer_id;
这条语句在所有主流数据库(PostgreSQL、SQL Server、Oracle)里都会报错,典型提示是:aggregate functions are not allowed in WHERE 或 invalid use of aggregate function。
- MySQL 5.7+ 在宽松模式下可能不报错,但行为不可靠,跨库迁移时必崩
- 即使侥幸执行,结果也常不符合预期——因为
WHERE根本没能力访问聚合值 - 别名在
HAVING中是否可用取决于数据库:MySQL 允许HAVING total > 5000(如果SELECT SUM(amount) AS total),但 PostgreSQL 要求写HAVING SUM(amount) > 5000
性能差异来自数据处理阶段不同
WHERE 过滤越早,后续要分组、聚合的数据就越少;HAVING 是对已经分好组的结果再筛,无法减少分组本身的开销。
- 想查“2024 年下单且订单数超 5 的客户”,
WHERE order_date >= '2024-01-01'必须写在GROUP BY前,否则COUNT(*)会包含历史订单 - 如果把时间条件错放到
HAVING,不仅逻辑错误,还会让数据库对全部年份数据做分组,白白消耗内存和 CPU - 原表有索引的字段(如
created_at、user_id)只在WHERE阶段能用上,HAVING完全无法利用索引加速
真正容易被忽略的点在于:HAVING 看不见 WHERE 没放进去的字段,也看不见未参与分组或聚合的列——它只认 GROUP BY 列和聚合表达式本身。一旦混淆作用域,查出来的结果可能完全对不上业务含义。











