HAVING 是什么
HAVING 用于对分组之后的结果做过滤,也就是「过滤组」而不是「过滤行」,执行顺序位于 GROUP BY 之后、ORDER BY 之前。能提前到 WHERE 的条件一定要下推,HAVING 里只保留必须依赖聚合结果的判断,否则会先完成大量分组再把结果丢掉,代价明显更高。
基本信息
| 用途 | 分组结果过滤 |
|---|---|
| 执行位置 | GROUP BY 之后、ORDER BY 之前 |
| 与 WHERE 的区别 | WHERE 过滤行,HAVING 过滤组 |
| 别名引用 | MySQL 支持引用 SELECT 别名,标准 SQL 更严格 |
| 性能要点 | 普通条件尽量写进 WHERE |
典型使用场景
- 筛选下单次数超过阈值的用户
- 找出累计金额达到门槛的客户
- 配合 COUNT、SUM、AVG 做分组后的结果收敛
- 多维度统计后再过滤,例如按渠道加地区分组
- 排查「把条件写在 HAVING 里导致查询变慢」
用法示例
用法示例
SELECT user_id, COUNT(*) AS cnt, SUM(amount) AS total
FROM orders
WHERE created_at >= '2026-09-01' -- 先筛行:能下推的都放这里
GROUP BY user_id
HAVING cnt >= 5 AND total > 1000 -- 再筛组
ORDER BY total DESC
LIMIT 20;
-- 可移植写法:不引用别名,直接写聚合表达式
-- HAVING COUNT(*) >= 5 AND SUM(amount) > 1000WHERE 与 HAVING 对照
| 子句 | 作用时机 | 注意事项 |
|---|---|---|
| WHERE | 分组前过滤行 | 能用索引,代价最低 |
| HAVING | 分组后过滤组 | 只能引用分组列与聚合函数 |
| 别名引用 | HAVING 中引用 SELECT 别名 | MySQL 支持,跨库不保证 |
| 聚合条件 | COUNT / SUM / AVG 等 | 无法下推到 WHERE |
| 组合使用 | 先 WHERE 再 GROUP BY 再 HAVING | 顺序不能颠倒 |
把普通条件写进 HAVING 会让引擎先完成全部分组再丢弃结果:执行代价随分组数量增长,而写在 WHERE 里可以利用索引提前缩小数据量。改写时先自问一句:这个条件能不能在分组之前就判断完。
MySQL 允许 HAVING 引用 SELECT 别名,这让 SQL 更易读;但跨数据库或严格模式下可能不兼容。对可移植性要求高的公共脚本,建议直接写聚合表达式本身,读起来长一点但更省心。
HAVING 中引用未分组的普通列存在语义歧义:开启 ONLY_FULL_GROUP_BY 后会直接报错,这通常说明 SQL 本身写得不够明确,应该补全分组列。