SQL HAVING 是什么?一篇讲清 WHERE 和 HAVING 的区别

原文链接: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(*) 这个概念。

它真正的意思是:

  1. 先按 customer_id 分组
  2. 再统计每个客户的订单数
  3. 最后只保留订单数大于等于 2 的客户

所以,HAVING 过滤的不是行,而是组。

2. 为什么 COUNT 不能写在 WHERE 里

这是初学者最常见的错误:

sql 复制代码
-- 错误写法
SELECT
  customer_id,
  COUNT(*) AS total_orders
FROM orders
WHERE COUNT(*) >= 2
GROUP BY customer_id;

为什么错?

因为 SQL 的执行顺序决定了:

  1. FROM
  2. WHERE
  3. GROUP BY
  4. HAVING
  5. SELECT
  6. ORDER 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;

它的执行逻辑就变成了:

  1. WHERE order_status = 'paid' 先过滤掉不是已支付的订单
  2. GROUP BY customer_id 再对剩下的数据按客户分组
  3. 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 订单,保留

最终结果就是客户 101103

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 应该一起用,而不是互相替代

很多初学者会把 WHEREHAVING 当成二选一,其实在实际工作里,好的聚合查询往往两者都会出现。

例如:

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 很适合拿来理解 WHEREGROUP BYHAVING 三者的分工:

  • 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 经常一起出现

如果你总是分不清 WHEREHAVING,不要死记语法,直接问自己两个问题:

  1. 我现在筛选的是原始行,还是分组结果?
  2. 这个条件依赖聚合值吗?

这两个问题想清楚,大多数 HAVING 场景你都不会再写错。

延伸阅读

相关推荐
weixin_568996061 小时前
c++如何实现日志文件的异步落盘功能_基于无锁队列方案【附代码】
jvm·数据库·python
tongyiixiaohuang2 小时前
技术案例分享:金蝶云星空客户数据同步到MySQL的实现
android·数据库·mysql
淘矿人2 小时前
2026年4月-DeepSeek V4 vs GPT-5.5深度对比测评:weelinking一键切换实测
服务器·数据库·人工智能·python·gpt·学习·php
2401_831419442 小时前
CSS 悬停箭头闪烁下移问题的根源与稳定解决方案
jvm·数据库·python
reasonsummer2 小时前
【教学类-160-11】20260419 AI视频培训-练习011“豆包AI视频《佛源植语》+豆包图片风格:无(关键词:藏传唐卡)”
数据库·音视频·豆包
瀚高PG实验室3 小时前
pgroonga全文检索插件的BUG
数据库·postgresql·bug·瀚高数据库
Rick19933 小时前
mysql 慢查询如何快速定位
数据库·mysql
2401_833033623 小时前
c++如何实现简单的文件签名验证_HMAC-SHA1算法应用【进阶】
jvm·数据库·python
qq_392690663 小时前
SQL报表查询标准规范_SQL书写规范优化
jvm·数据库·python