HAVING:分组后过滤
本教程共 50 篇 · 第 25 篇 · 更新于 2026-07-31
25. HAVING:分组后过滤
本节目标:学完本章你能用 HAVING 筛选出”满足条件的分组”,并清楚解释 HAVING 与 WHERE 到底差在哪、为什么聚合条件只能写在 HAVING 里。
为什么 WHERE 管不了”分组之后”的事
上一章我们用 GROUP BY 算出了”每个用户的订单数、总消费”。现在来个新需求:只想知道那些消费总额超过 1000 的用户。
直觉上你可能想这么写:
sqlite> SELECT user_id, SUM(amount) AS total
...> FROM orders
...> WHERE SUM(amount) > 1000
...> GROUP BY user_id;
但这条语句会报错。原因很本质:WHERE 是在分组之前、对”每一行原始数据”做过滤的。而 SUM(amount) > 1000 这种条件依赖”分组之后才算得出来的聚合值”,WHERE 阶段根本还没有”组”的概念,所以它看不懂 SUM(amount)。
这就需要 HAVING:它是专门在分组完成之后,对”每一个组”做过滤的子句。
HAVING 的基本写法
sqlite> SELECT user_id, COUNT(*) AS order_count, SUM(amount) AS total
...> FROM orders
...> GROUP BY user_id
...> HAVING SUM(amount) > 1000;
执行流程是:FROM 取表 → WHERE 过滤行 → GROUP BY 分组 → HAVING 对组过滤 → SELECT 输出。只有那些 SUM(amount) > 1000 的组,才会留在最终结果里。
HAVING 里的条件可以是:
- 聚合表达式:如
HAVING COUNT(*) > 5、HAVING AVG(age) < 30; - 分组列本身:如
HAVING user_id = 1(不过这种能用WHERE先过滤的,通常放WHERE更高效); - 带普通列的比较,但更常见的还是配合聚合函数。
WHERE 与 HAVING 的核心区别
| 维度 | WHERE | HAVING |
|---|---|---|
| 作用阶段 | 分组之前 | 分组之后 |
| 作用对象 | 原始行(row) | 分组(group) |
| 能不能用聚合函数 | 不能(如 COUNT(*)) | 能(如 SUM(amount)) |
| 典型用途 | 过滤”哪些行参与统计” | 过滤”哪些组保留下来” |
一句话:WHERE 决定”拿哪些原料进锅”,HAVING 决定”哪些做好的菜能端上桌”。
一个综合例子,先 WHERE 缩小时间范围,再 GROUP BY 分组,最后 HAVING 筛组:
sqlite> SELECT user_id, SUM(amount) AS total
...> FROM orders
...> WHERE created_at >= '2026-01-01'
...> GROUP BY user_id
...> HAVING SUM(amount) > 500
...> ORDER BY total DESC;
这里 created_at 是 TEXT 类型存的日期(记住 SQLite 没有 DATE 类型,我们用 ISO8601 文本存),WHERE 先只保留今年的订单,再按用户汇总,HAVING 只留消费超 500 的用户,最后按金额降序排列。三个子句各司其职,顺序不能乱。
HAVING 一定要配合 GROUP BY 吗
在 SQLite 里,HAVING 并非”必须”紧跟 GROUP BY。如果没有 GROUP BY,SQLite 会把整张结果集当作一个组,此时 HAVING 作用在”这一整个组”上:
sqlite> SELECT SUM(amount) AS total FROM orders HAVING SUM(amount) > 1000;
这条语句把全部订单当一个组,只有总消费超过 1000 才返回结果,否则返回空。虽然语法允许,但日常写作中 HAVING 几乎总是和 GROUP BY 成对出现——因为它存在的意义就是”按组分完再筛”。如果你只想筛整表聚合,往往直接把条件写进 WHERE(当条件不涉及聚合时)或更直观的写法即可,不必硬套 HAVING。
Warning常见坑:想在聚合结果上做过滤时,千万别把聚合条件写进
WHERE。WHERE COUNT(*) > 1会直接报错,因为WHERE阶段还没有聚合结果。正确写法是GROUP BY ... HAVING COUNT(*) > 1。这是新手最高频的错误之一,记住”聚合条件进 HAVING,行级条件进 WHERE”。
Tip实用技巧:如果
HAVING里的聚合表达式在SELECT里已经起了别名(如SUM(amount) AS total),有的数据库允许在HAVING里直接用别名HAVING total > 1000,但 SQLite 对别名的支持有限、行为依赖版本,最稳妥的写法是在HAVING里重复写一遍聚合表达式HAVING SUM(amount) > 1000,不要依赖别名,可移植性最好。
HAVING 里用分组列还是聚合值
HAVING 两种都能用,但语义不同:
HAVING user_id = 3:只保留user_id = 3那一组(本质是行级过滤,放在WHERE更高效);HAVING COUNT(*) > 3:保留”订单数大于 3”的所有组(真正的分组后过滤)。
大多数有意义的业务过滤是后者——例如”找出下单超过 3 次的高频用户""找出月消费都高于某阈值的用户”。
适用场景小结
- 按组筛阈值:高频用户、高消费用户、低库存分类等,凡是”分组统计值满足某条件”的需求。
- 找异常组:如”订单数为 0 的用户”(配合
HAVING COUNT(*) = 0需左连接场景)、“平均金额异常偏高的分类”。 - 与 WHERE 配合:
WHERE先收窄数据范围(减少计算量),HAVING再筛组,效率高且语义清晰。 - 报表里的”Top N 门槛”:后台常要”消费前 100 名里、且下单超过 5 次的人”,这类”先排名、再设门槛”的需求,几乎都是
GROUP BY+HAVING的标准组合。
一个稍复杂的例子:既高频又高消费的用户
HAVING 里可以同时放多个聚合条件,用 AND/OR 连接。比如”找出下单超过 3 次、且总消费超过 500 的用户”:
sqlite> SELECT user_id, COUNT(*) AS c, SUM(amount) AS total
...> FROM orders
...> GROUP BY user_id
...> HAVING COUNT(*) > 3 AND SUM(amount) > 500
...> ORDER BY total DESC;
只有”订单数”和”总金额”两个门槛同时跨过的组,才会出现在结果里。这种”在分组结果上做多条件筛选”的能力,正是 HAVING 的用武之地——而这些条件里用到的 COUNT(*)、SUM(amount) 都只能在分组之后才能算出来,放进 WHERE 必然报错。
执行顺序回顾:WHERE 与 HAVING 各站哪一步
把带 WHERE 和 HAVING 的完整查询拆开看,标准执行顺序是:
FROM取出要查的表;WHERE过滤掉不需要的原始行(行级条件,不能用聚合);GROUP BY把剩下的行按分组键归类;- 对每个组计算
SELECT里的聚合函数; HAVING按组级条件再筛一次(可用聚合);SELECT决定输出哪些列,ORDER BY最后排序。
记住”WHERE 在第 2 步、HAVING 在第 5 步”,就能解释一切:为什么聚合条件只能进 HAVING,为什么 WHERE 里出现的列可以不进 GROUP BY 而 HAVING 引用的非聚合列通常得进 GROUP BY。
常见错误对照
| 想做的事 | 错误写法(报错/逻辑错) | 正确写法 |
|---|---|---|
| 筛订单数大于 3 的组 | WHERE COUNT(*) > 3 | GROUP BY ... HAVING COUNT(*) > 3 |
| 只保留总消费超 500 的组 | WHERE SUM(amount) > 500 | HAVING SUM(amount) > 500 |
| 先缩小时间再分组 | HAVING created_at >= '2026-01-01' | WHERE created_at >= '2026-01-01' |
Note重点提示:
HAVING不是WHERE的替代品,而是互补品。凡是”针对一行原始数据”的判断(有没有这个值、日期在不在这个范围)放WHERE;凡是”针对一组汇总结果”的判断(平均值多大、总数多少)放HAVING。把两者摆对位置,查询既正确又高效。
类比小结
把 GROUP BY 想成”先按班级把学生分队”,WHERE 是”进场前先刷掉不符合年龄的人”,HAVING 是”队伍分好后,只让’平均分超过 80 的班级’留下来领奖”。WHERE 动的是个人、HAVING 动的是整队;一旦条件里出现 COUNT/SUM/AVG 这类”只有组才有的数字”,它就一定属于 HAVING,写进 WHERE 必然报错。