WHERE过滤分组前的行,HAVING过滤分组后的组;WHERE不能用聚合函数,HAVING只能用GROUP BY列、聚合函数及衍生表达式;HAVING通常需配合GROUP BY,性能优化重点在WHERE下推和GROUP BY索引。

WHERE 和 HAVING 到底该用哪个?
WHERE 过滤行,HAVING 过滤组——这是最本质的区别。写错位置会导致语法错误或逻辑错乱,比如在 HAVING 里引用未聚合的原始列(如 user_id),或者在 WHERE 里用 COUNT(*) 这类聚合函数,数据库会直接报错:ERROR: column "xxx" must appear in the GROUP BY clause or be used in an aggregate function。
常见错误现象:
- 想筛出“订单数大于 5 的用户”,却在
WHERE里写COUNT(order_id) > 5→ 报错 - 分组后想排除某类用户(如
status = 'inactive'),却放在HAVING里 → 逻辑错误(状态是单行属性,不该等分组完再筛)
记住:WHERE 在分组前执行,HAVING 在分组后执行;能放 WHERE 的,别硬塞进 HAVING。
HAVING 必须配合 GROUP BY 吗?
绝大多数情况下是的。标准 SQL 要求 HAVING 出现时必须有 GROUP BY,否则语法不合法。但 PostgreSQL 和 MySQL(8.0+)允许无 GROUP BY 的 HAVING,此时整张表被当作一个组处理——这容易引发误解,尤其当表为空时,HAVING COUNT(*) > 0 会返回空结果而非报错,行为隐蔽。
实操建议:
- 显式写出
GROUP BY,哪怕只按常量分组(如GROUP BY 1),可读性更强 - 避免依赖“隐式单组”行为,跨数据库迁移时极易出问题
- 如果只是想对全表聚合结果做判断(如“总销售额是否超百万”),优先用子查询或 CTE,语义更清晰
在 HAVING 中能用哪些字段?
只能用三类内容:GROUP BY 列、聚合函数(如 COUNT()、SUM()、AVG())、以及由它们组成的表达式(如 AVG(price) * 1.1)。不能出现未聚合的普通列,也不能用别名(多数数据库不支持 HAVING 中引用 SELECT 别名)。
典型陷阱:
- 写
SELECT user_id, COUNT(*) AS cnt FROM orders GROUP BY user_id HAVING cnt > 5→ 多数数据库不认cnt,得写成HAVING COUNT(*) > 5 - 想按“平均单价 > 100”筛选,却写
HAVING price > 100→ 报错,必须写HAVING AVG(price) > 100 - MySQL 5.7 默认开启
ONLY_FULL_GROUP_BY,会严格校验,而旧版本可能静默返回错误结果
性能和索引对 HAVING 有影响吗?
没有直接影响。因为 HAVING 是在内存或临时结果集上过滤分组结果,不走索引。真正影响性能的是 GROUP BY 本身——它需要排序或哈希分组,数据量大时很耗资源。所以优化重点不在 HAVING 条件写法,而在前置减少参与分组的数据量。
实操建议:
- 把能下推的过滤条件尽量放进
WHERE(如时间范围、状态码),大幅缩减分组输入行数 - 确保
GROUP BY字段上有索引(尤其是复合索引,顺序要匹配分组字段顺序) -
HAVING条件本身尽量简单,避免在聚合结果上再套复杂函数(如HAVING UPPER(MAX(name)) LIKE '%A%')
很多人盯着 HAVING 写法调优,其实瓶颈早就在 GROUP BY 前就埋下了。

















