首页 / SQLite 入门教程 / HAVING:分组后过滤

SQLite 入门教程

HAVING:分组后过滤

本教程共 50 篇 · 第 25 篇 · 更新于 2026-07-31

sqliteHAVINGGROUP BY分组过滤聚合过滤

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(*) > 5HAVING AVG(age) < 30
  • 分组列本身:如 HAVING user_id = 1(不过这种能用 WHERE 先过滤的,通常放 WHERE 更高效);
  • 带普通列的比较,但更常见的还是配合聚合函数。

WHERE 与 HAVING 的核心区别

维度WHEREHAVING
作用阶段分组之前分组之后
作用对象原始行(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_atTEXT 类型存的日期(记住 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

常见坑:想在聚合结果上做过滤时,千万别把聚合条件写进 WHEREWHERE 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 各站哪一步

把带 WHEREHAVING 的完整查询拆开看,标准执行顺序是:

  1. FROM 取出要查的表;
  2. WHERE 过滤掉不需要的原始行(行级条件,不能用聚合);
  3. GROUP BY 把剩下的行按分组键归类;
  4. 对每个组计算 SELECT 里的聚合函数;
  5. HAVING 按组级条件再筛一次(可用聚合);
  6. SELECT 决定输出哪些列,ORDER BY 最后排序。

记住”WHERE 在第 2 步、HAVING 在第 5 步”,就能解释一切:为什么聚合条件只能进 HAVING,为什么 WHERE 里出现的列可以不进 GROUP BYHAVING 引用的非聚合列通常得进 GROUP BY

常见错误对照

想做的事错误写法(报错/逻辑错)正确写法
筛订单数大于 3 的组WHERE COUNT(*) > 3GROUP BY ... HAVING COUNT(*) > 3
只保留总消费超 500 的组WHERE SUM(amount) > 500HAVING 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 必然报错。