首页/数据/SQL 速查/HAVING 过滤分组结果
聚合与窗口函数54 条

HAVING 过滤分组结果

HAVING 在分组与聚合之后过滤:条件里可以用聚合函数,HAVING COUNT(*) > 5 是典型用法。

SQL 速查数据更新于 2026-10-13

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 分工

维度WHEREHAVING
过滤对象行分组结果
执行时机分组与聚合之前分组与聚合之后
能否用聚合函数不能可以
能否用 SELECT 别名不能可以
对代价的影响直接减少扫描与聚合量不减少聚合量
典型写法WHERE status = 1HAVING 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 里尽量只用聚合函数与分组列,不要引用未分组的列,否则结果会依赖数据库的实现细节,换个版本或换个库就可能变化。

易错点

  • 把行级过滤写进 HAVING,全表参与聚合后再筛,代价高且语义容易与预期不符
  • 在 WHERE 里写聚合函数导致语法报错,误以为是数据库版本问题
  • 忘了 GROUP BY 就直接用 HAVING,整表被当作一组,结果只剩一行
  • HAVING 条件与 SELECT 别名同名但表达式不同,改口径时只改了一处
  • 用 HAVING 过滤掉本该展示的分组,报表总数与明细对不上

数据来源与参考

  • MySQL 8.0 Reference Manual · HAVING
  • PostgreSQL Documentation · HAVING
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌