原文链接:https://www.hisqlboy.com/blog/sql-having-clause-explained
适合人群:SQL 初学者、数据分析初学者、需要写聚合查询的后端开发者
很多人第一次写聚合查询时,都会卡在同一个地方:
WHERE 可以过滤行,那聚合之后的结果该怎么过滤?
比如你已经会写"统计每个客户的订单数",但一旦题目变成"只保留订单数大于等于 2 的客户",不少人会下意识把 COUNT(*) >= 2 写进 WHERE,然后 SQL 直接报错。
这时候该用的不是 WHERE,而是 HAVING。
一句话先记住:
WHERE用来过滤原始行HAVING用来过滤聚合后的分组结果
1. HAVING 到底是做什么的
HAVING 的作用,是在 GROUP BY 之后,对已经形成的分组进行筛选。
最常见的写法如下:
sql
SELECT
customer_id,
COUNT(*) AS total_orders
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 2;
这段 SQL 的意思不是"找出 COUNT(*) >= 2 的行",因为在单行层面根本没有 COUNT(*) 这个概念。
它真正的意思是:
- 先按
customer_id分组 - 再统计每个客户的订单数
- 最后只保留订单数大于等于 2 的客户
所以,HAVING 过滤的不是行,而是组。
2. 为什么 COUNT 不能写在 WHERE 里
这是初学者最常见的错误:
sql
-- 错误写法
SELECT
customer_id,
COUNT(*) AS total_orders
FROM orders
WHERE COUNT(*) >= 2
GROUP BY customer_id;
为什么错?
因为 SQL 的执行顺序决定了:
FROMWHEREGROUP BYHAVINGSELECTORDER BY
也就是说,执行到 WHERE 的时候,分组和聚合都还没有发生,COUNT(*) 这种聚合结果根本还不存在。
所以你可以这样理解:
WHERE工作在"明细行"阶段HAVING工作在"分组汇总"阶段
3. WHERE 和 HAVING 到底有什么区别
先看一个最典型的例子。
sql
SELECT
customer_id,
COUNT(*) AS total_orders
FROM orders
GROUP BY customer_id
HAVING COUNT(*) >= 2
ORDER BY total_orders DESC;
这个查询会返回"订单数不少于 2 的客户"。
如果再加上 WHERE:
sql
SELECT
customer_id,
COUNT(*) AS total_orders
FROM orders
WHERE order_status = 'paid'
GROUP BY customer_id
HAVING COUNT(*) >= 2
ORDER BY total_orders DESC;
它的执行逻辑就变成了:
WHERE order_status = 'paid'先过滤掉不是已支付的订单GROUP BY customer_id再对剩下的数据按客户分组HAVING COUNT(*) >= 2最后只保留已支付订单数不少于 2 的客户
这也是最容易记住的区分方式:
WHERE:哪些原始数据可以参与统计HAVING:哪些统计结果可以保留下来
4. 用一个完整示例理解 HAVING
假设有一张订单表:
sql
CREATE TABLE orders_having_demo (
order_id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL,
order_status TEXT NOT NULL,
amount INTEGER NOT NULL
);
INSERT INTO orders_having_demo VALUES
(1001, 101, 'paid', 120),
(1002, 101, 'paid', 80),
(1003, 102, 'pending', 40),
(1004, 103, 'paid', 150),
(1005, 103, 'paid', 60),
(1006, 103, 'refunded', 60);
现在想统计"付款订单数不少于 2 的客户",SQL 可以这样写:
sql
SELECT
customer_id,
COUNT(*) AS total_orders
FROM orders_having_demo
WHERE order_status = 'paid'
GROUP BY customer_id
HAVING COUNT(*) >= 2
ORDER BY total_orders DESC;
分析一下:
customer_id = 101有 2 笔paid订单,保留customer_id = 102没有paid订单,被过滤customer_id = 103有 2 笔paid订单,保留
最终结果就是客户 101 和 103。
5. HAVING 不只是配 COUNT,还可以配 SUM、AVG
很多人一提到 HAVING,脑子里只有 COUNT(*)。其实它和所有聚合函数都能一起用。
5.1 按收入过滤分组
sql
SELECT
customer_id,
SUM(amount) AS revenue
FROM orders_having_demo
GROUP BY customer_id
HAVING SUM(amount) >= 200
ORDER BY revenue DESC;
这个查询的意思是:
按客户汇总销售额,然后只保留总金额大于等于 200 的客户。
5.2 按部门人数过滤
sql
SELECT
department,
COUNT(*) AS headcount
FROM employees
GROUP BY department
HAVING COUNT(*) >= 5;
这类写法在报表、运营分析、业务阈值筛选里都非常常见。
6. WHERE 和 HAVING 应该一起用,而不是互相替代
很多初学者会把 WHERE 和 HAVING 当成二选一,其实在实际工作里,好的聚合查询往往两者都会出现。
例如:
sql
SELECT
customer_id,
SUM(amount) AS paid_revenue
FROM orders_having_demo
WHERE order_status = 'paid'
GROUP BY customer_id
HAVING SUM(amount) >= 150;
这里:
WHERE负责把无关的原始行提前过滤掉HAVING负责在汇总结果出来之后继续筛选
这样写通常更清晰,也更符合数据库处理数据的方式。
7. 一个更接近业务场景的例子
假设你要做一个报表,统计"已支付订单数不少于 3,并且活跃客户数不少于 2 的城市"。
sql
SELECT
city,
COUNT(DISTINCT customer_id) AS active_customers,
COUNT(*) AS paid_orders
FROM city_orders_having
WHERE order_status = 'paid'
GROUP BY city
HAVING COUNT(DISTINCT customer_id) >= 2
AND COUNT(*) >= 3
ORDER BY paid_orders DESC, city ASC;
这条 SQL 很适合拿来理解 WHERE、GROUP BY、HAVING 三者的分工:
WHERE:先只看已支付订单GROUP BY city:把数据按城市汇总HAVING:只保留满足业务门槛的城市
这比单纯记语法更重要,因为实际工作中你写的很多查询,本质上都是这种"先筛行,再汇总,最后筛组"的过程。
8. 使用 HAVING 时最常见的 3 个错误
错误 1:把本该写在 WHERE 里的条件写进 HAVING
例如:
sql
SELECT
customer_id,
SUM(amount) AS revenue
FROM orders
GROUP BY customer_id
HAVING customer_id > 100;
这条 SQL 不一定不能跑,但这个条件并不依赖聚合结果,更适合提前写到 WHERE:
sql
SELECT
customer_id,
SUM(amount) AS revenue
FROM orders
WHERE customer_id > 100
GROUP BY customer_id;
这样通常更直观,也更高效,因为进入分组步骤的数据更少。
错误 2:分组后忘了结果的粒度已经变了
一旦你写了:
sql
GROUP BY city
那结果集里的每一行就代表一个城市,而不是一条订单。
如果这一点没想清楚,就很容易误读统计结果。
错误 3:把 HAVING 理解成"高级版 WHERE"
这会导致概念越学越乱。
HAVING 不是更高级的 WHERE,而是在另一个阶段过滤另一种对象:
WHERE过滤原始行HAVING过滤聚合后的组
9. 什么时候该用 HAVING
可以直接记这个判断方法:
如果你的筛选条件依赖下面这些聚合结果之一,就优先考虑 HAVING:
COUNT(...)SUM(...)AVG(...)MIN(...)MAX(...)
比如:
- 订单数至少 2 的客户
- 销售额超过 10000 的品类
- 平均评分高于 4.5 的商品
这些都属于"先汇总,再筛选"的场景。
10. 总结
最后用几句话把它彻底记住:
WHERE过滤的是分组前的原始数据HAVING过滤的是聚合后的分组结果COUNT(*)、SUM(amount)这类聚合条件,通常应该写在HAVING- 实际工作里,
WHERE + GROUP BY + HAVING经常一起出现
如果你总是分不清 WHERE 和 HAVING,不要死记语法,直接问自己两个问题:
- 我现在筛选的是原始行,还是分组结果?
- 这个条件依赖聚合值吗?
这两个问题想清楚,大多数 HAVING 场景你都不会再写错。