where在分组前过滤行数据,having在分组后过滤聚合结果;前者可使用原始字段和索引优化,后者仅能引用group by列或聚合函数,且必须依赖group by。

WHERE和HAVING根本不是同一层的东西
它们不是“能换着用”的两个选项,而是 SQL 执行流水线里前后衔接、职责分明的两个环节。WHERE 处理的是单行原始数据,HAVING 处理的是分组后的聚合结果——就像工厂里“质检员(WHERE)在零件组装前检查每个螺丝,而主管(HAVING)在整台机器装配完后验收成品”。把 HAVING 当成 WHERE 的替代品,等于让主管去拧螺丝,逻辑错位,性能也崩。
HAVING 无法过滤未进入分组的行
常见错误是写 HAVING status = 'paid' 代替 WHERE status = 'paid'。这会导致数据库先把所有状态(包括 'cancelled'、'pending')的订单全分组、全聚合一遍,再把非 'paid' 的组整个丢掉——白算。
-
WHERE在GROUP BY前执行:不满足条件的行直接剔除,根本不参与分组和聚合 -
HAVING在GROUP BY后执行:它看到的已经是分好组、算好COUNT(*)和SUM(amount)的结果,对“行”已无感知 - 想筛出“2024年已支付订单数 ≥ 5 的客户”,必须用
WHERE order_date >= '2024-01-01' AND status = 'paid'先缩小范围,再HAVING COUNT(*) >= 5
HAVING 依赖 GROUP BY 存在,且作用域极窄
HAVING 不是独立子句,它没有 GROUP BY 就失去意义。某些数据库(如旧版 MySQL)允许 SELECT COUNT(*) HAVING COUNT(*) > 10,但这只是把整张表当一个隐式组处理,跨库不可靠,且掩盖了真实意图。
-
HAVING只能引用两类东西:出现在GROUP BY中的字段(如dept_id),或聚合函数(如AVG(salary)) -
HAVING amount > 100一定报错,因为amount是明细字段,每组有多个值,无法直接比 -
HAVING不能用未在SELECT或GROUP BY中出现的别名(PostgreSQL/SQL Server 不认HAVING total > 10000,除非total是聚合表达式本身)
WHERE 能干的事,HAVING 真的干不了
WHERE 可用于 UPDATE、DELETE、INSERT ... SELECT,而 HAVING 只存在于 SELECT 查询中。更关键的是,WHERE 支持索引下推、分区裁剪等底层优化,HAVING 则只能在内存中对聚合结果做最后筛选——它天生慢一步,也少一层能力。
- 带
IN、BETWEEN、LIKE、函数索引字段(如WHERE created_at > NOW() - INTERVAL '7 days')的条件,几乎只能放WHERE -
HAVING无法利用 B-tree 索引加速,因为它操作的是临时分组结构,不是原始表 - MySQL 5.7+ 开启
ONLY_FULL_GROUP_BY后,HAVING里混用非分组字段会直接报错,不是 bug,是强制你厘清逻辑边界
真正容易被忽略的,是执行顺序固化带来的刚性约束:WHERE 的时机决定了它必须负责“减量”,HAVING 的时机决定了它只能负责“择优”。想绕开这个分工,不是语法技巧问题,而是对 SQL 运行模型的根本误读。











