HAVING 是什么
HAVING 在分组与聚合之后过滤,条件里可以直接写聚合函数,是「先算再筛」的典型场景。它与 WHERE 的分工很明确:WHERE 过滤行,决定哪些行参与分组;HAVING 过滤组,决定哪些组留下。能写进 WHERE 的条件不要写 HAVING,因为 HAVING 无法减少参与聚合的数据量。MySQL 与 PostgreSQL 都允许 HAVING 引用 SELECT 里的列别名,这点与 WHERE 不同。没有 GROUP BY 时整表被当作一个分组,HAVING 依然可以起作用。
基本信息
| 作用 | 过滤分组后的结果,条件里可以写聚合函数 |
|---|---|
| 执行时机 | GROUP BY 之后、ORDER BY 之前 |
| 可用内容 | 聚合函数、分组列、SELECT 里的列别名 |
| 与 WHERE 的分工 | WHERE 过滤行,HAVING 过滤组 |
| 无 GROUP BY 时 | 整表视为一个分组,HAVING 仍可用 |
| 适用平台 | 标准 SQL,MySQL 8.0 与 PostgreSQL 一致 |
典型使用场景
- 筛高频用户:HAVING COUNT(*) >= 5 找出下单五次以上的人
- 筛高价值分组:HAVING SUM(amount) > 10000 找出大客户
- 数据质量检查:HAVING COUNT(*) > 1 找出本该唯一却重复的记录
- 筛分组均值:HAVING AVG(score) < 60 找出平均分不及格的班级
- 配合别名:HAVING total > 1000,total 是 SUM(amount) 的别名
- 多条件组合:同时限制组内行数与组内金额,精确定位目标分组
示例
示例
SELECT user_id,
COUNT(*) AS order_cnt, # 组内订单数
SUM(amount) AS total_amount # 组内金额
FROM orders
WHERE status = 'paid' # 先过滤行,减少参与聚合的数据量
GROUP BY user_id
HAVING COUNT(*) >= 3 # 再过滤组
AND SUM(amount) > 1000
ORDER BY total_amount DESC;该写 WHERE 还是 HAVING
该写 WHERE 还是 HAVING
-- 两句结果相同,第一句更快:过滤发生在聚合之前
SELECT city FROM users WHERE status = 1 GROUP BY city;
SELECT city FROM users GROUP BY city HAVING MAX(status) = 1; # 不推荐
-- HAVING 可以引用 SELECT 里的别名
SELECT city, SUM(amount) AS total
FROM orders
GROUP BY city
HAVING total > 1000; # 多数数据库支持,MySQL 与 PostgreSQL 都可用WHERE 与 HAVING 分工
| 维度 | WHERE | HAVING |
|---|---|---|
| 过滤对象 | 行 | 分组结果 |
| 执行时机 | 分组与聚合之前 | 分组与聚合之后 |
| 能否用聚合函数 | 不能 | 可以 |
| 能否用 SELECT 别名 | 不能 | 可以 |
| 对代价的影响 | 直接减少扫描与聚合量 | 不减少聚合量 |
| 典型写法 | WHERE status = 1 | HAVING COUNT(*) > 5 |
聚合条件写在 WHERE 里会直接报错(Invalid use of group function 之类),这是语法层面的硬约束,不是性能建议。
HAVING 与 WHERE 可以同时出现:先 WHERE 缩小参与聚合的行,再 HAVING 筛组,这是效率最好的写法。
只在 HAVING 里做行级过滤会让全表参与聚合,大表上表现为临时表膨胀与磁盘排序,排查慢报表时优先检查这一处。
PostgreSQL 要求非聚合的 HAVING 条件必须依赖分组列,MySQL 在 ONLY_FULL_GROUP_BY 下也是同样要求,跨库写法保持一致更省事。
实践要点
- 过滤顺序从紧到松:先把能大幅缩小数据量的条件放 WHERE
- HAVING 里避免复杂表达式:能在 SELECT 里算好别名再引用更清楚
- 分组键上建索引:减少分组时的临时表与排序
- 报表明细先看组数:组数过多时考虑先按更粗粒度预聚合
- 把口径写进注释:COUNT(*) 与 COUNT(DISTINCT …) 的差异要在脚本里说明
- HAVING 与 ORDER BY 的分工:过滤用 HAVING,展示顺序交给 ORDER BY
常见疑问
- HAVING 能引用别名吗:MySQL 与 PostgreSQL 都允许,写在 HAVING 或 ORDER BY 里都可以
- 没有 GROUP BY 能用 HAVING 吗:可以,整表被视为一个分组,结果通常只剩一行
- HAVING 里能用窗口函数吗:不能,窗口函数求值在 HAVING 之后,需要套一层子查询
- WHERE 与 HAVING 谁先执行:WHERE 先执行,这正是它能大幅减少聚合数据量的原因
- HAVING 里的条件能走索引吗:一般不能,索引在扫描阶段起作用,HAVING 已是结果层过滤
- 组数很多时怎么优化:先按更粗的粒度预聚合,或把汇总结果落到中间表按需刷新
HAVING 里尽量只用聚合函数与分组列,不要引用未分组的列,否则结果会依赖数据库的实现细节,换个版本或换个库就可能变化。