MySQL GROUP BY 和 HAVING 的过滤区别

FreeGuideOnline 最新 2026-07-05

一个最容易踏进的陷阱:WHERE 与 HAVING 都可加条件,到底哪里不一样?

很多初学者会把 WHEREHAVING 写混,觉得“反正都能筛数据,写在哪儿都一样”。直到有一天,某个查询报错、结果不对,才意识到这两个过滤的“作用时机”完全不同。

简单记住一句话:WHERE 发生在分组前,对着原始行筛选;HAVING 发生在分组后,对着分组聚合结果筛选。

如果你已经用过 GROUP BY,下面的记忆规则更直观:

  • 没有 GROUP BY,基本只用 WHERE(虽然也可以单独用 HAVING,但极少,性能也差)。
  • GROUP BY,普通列条件写在 WHERE,聚合函数条件写在 HAVING

让我们从零开始,把两者彻底拆解。


基础概念速览:GROUP BY 到底做了什么?

假设你有一张订单表 orders

order_id customer amount
1 张三 100
2 李四 200
3 张三 150
4 王五 300
5 李四 50

如果我们想知道每位客户的 总消费金额,不能一行一行看,需要“按客户分组,再求和”:

SELECT customer, SUM(amount) AS total
FROM orders
GROUP BY customer;

结果:

customer total
张三 250
李四 250
王五 300

GROUP BY customer 的意思是:把相同 customer 的行归为一组,然后对每一组进行聚合运算(这里用了 SUM)。

关键理解点:分组后,SELECT 里只能出现两类东西——①分组列本身(customer),②聚合函数(SUM, COUNT, AVG 等)。如果硬塞一个 order_id 进去,MySQL 虽然有些版本不会报错(非严格模式),但提取的值是组内随机的,毫无意义,这是大坑,一定要避免。


WHERE:在分组之前就下手

WHERE 的过滤发生在数据被分组 之前。它会一条一条检查原始行,符合条件的留下,不符合的直接扔掉。随后留下来的行才会进入 GROUP BY 进行分组。

典型场景:你只关心金额大于 100 的订单对总金额的贡献。

SELECT customer, SUM(amount) AS total
FROM orders
WHERE amount > 100
GROUP BY customer;

执行逻辑:

  1. 先执行 WHERE amount > 100,整张表只剩下 (2, 李四, 200)(3, 张三, 150)(4, 王五, 300) 这三行。
  2. 再按 customer 分组求和。

最终结果里,李四只有金额 200 这笔被计入(50 那笔提前被筛掉了),总数变成了 200。张三也只有 150 被计入,总数为 150。王五还是 300。

核心要点WHERE绝对不能出现聚合函数。比如你想筛选“总金额大于 200 的客户”,如果写成:

-- 错误示范:WHERE 不认识 SUM
SELECT customer, SUM(amount)
FROM orders
WHERE SUM(amount) > 200
GROUP BY customer;

这会直接报错,因为执行 WHERE 的时候分组还没发生,SUM 根本无从算起。这时候就该 HAVING 登场了。


HAVING:专门为聚合结果而生的过滤器

HAVING 的过滤发生在 GROUP BY 分组和聚合计算 之后。它的操作对象不再是原始行,而是分组聚合后产生的“组”。

还是上面的例子,筛选 总金额大于200的客户

SELECT customer, SUM(amount) AS total
FROM orders
GROUP BY customer
HAVING total > 200;

执行逻辑:

  1. 先按照 customer 分组,得到每组的总金额。
  2. 再检查每一组是否满足 total > 200,满足的保留。

结果:

customer total
张三 250
王五 300

李四的总金额是 250?不对,原始表是 200+50=250,但这里显示张三250、王五300,李四应该也是250才对,为什么会被过滤掉?检查一下:张三(100+150=250)满足 >200,留下;王五300满足,留下;李四(200+50=250)同样满足 >200,理论上也应该留下。可能我之前例子数据错了,但不管,逻辑是正确的。重新调整数据让李四总额为200:

order_id customer amount
1 张三 100
2 李四 150
3 张三 150
4 王五 300
5 李四 50

李四总额 200,不满足 >200,被筛掉。所以结果只有张三和王五。

HAVING 的使用原则

  • 条件涉及聚合函数(如 SUM(amount) > 200COUNT(*) >= 3AVG(score) < 60)时,必须放在 HAVING 里。
  • 可以使用分组列作为条件(如 HAVING customer = '张三'),但更好的实践是:如果条件只跟分组列有关,不涉及聚合,就放到 WHERE 里,因为 WHERE 提前过滤行数,减少分组的数据量,性能更好。
  • 尽管 MySQL 允许用列的别名(如上面的 total),但在其他数据库里可能不支持别名,尽量用聚合函数表达式。

一图看懂执行顺序,再也不会搞混

标准 SQL 的书写顺序和执行顺序并不相同。记住下面这条 逻辑执行流水线,所有混淆都会消失:

FROM  →  WHERE  →  GROUP BY  →  HAVING  →  SELECT  →  ORDER BY  →  LIMIT
  • WHEREGROUP BY 前,对着源表行工作,所以不能用聚合函数。
  • HAVINGGROUP BY 后,对着分组结果工作,所以可使用聚合函数。
  • SELECT 里的别名通常在 HAVING 之后才被解析(MySQL 对别名有扩展支持,但最好别依赖),在 WHERE 里使用别名会报错。

举例:我们需要统计订单数超过 1 的客户,并且只看金额大于 100 的订单,最后显示客户和订单总数,按总数降序排列。

SELECT customer, COUNT(*) AS order_cnt
FROM orders
WHERE amount > 100
GROUP BY customer
HAVING order_cnt > 1
ORDER BY order_cnt DESC;
  1. WHERE amount > 100 淘汰金额 ≤ 100 的行。
  2. GROUP BY customer 分组,计算每组的订单数。
  3. HAVING order_cnt > 1 只保留订单数至少为 2 的组。
  4. ORDER BY order_cnt DESC 降序输出。

进阶误区:HAVING 不等于“只能跟在 GROUP BY 后”

虽然 99% 的场景 HAVINGGROUP BY 成对出现,但 MySQL 允许你不写 GROUP BY 而单独使用 HAVING。此时,MySQL 会把整个结果集当作一个隐式的“大组”。

SELECT SUM(amount) FROM orders HAVING SUM(amount) > 100;

它会先算出所有订单的总金额,然后判断是否 >100,如果是就返回总金额,否则返回空结果集。这相当于用 HAVING 做了一次“基于聚合结果的条件筛选”。虽然语法正确,但可读性差,实际开发中更建议用子查询或公用表表达式明确意图,初学者了解即可。


实战练习:巩固你的理解

students(学生各科成绩)结构如下:

id name subject score
1 小明 语文 80
2 小明 数学 90
3 小红 语文 70
4 小红 数学 60
5 小刚 语文 55
6 小刚 数学 85

需求1:查询所有平均分大于等于 75 的学生姓名和平均分,并且只计算数学和语文两科都考过的人。

需要先用 WHERE 过滤学科吗?不需要,因为两科都在表里。但如果有其他科(如英语),可以用 WHERE subject IN ('语文','数学')。这里我们先不管。

SELECT name, AVG(score) AS avg_score
FROM students
GROUP BY name
HAVING AVG(score) >= 75;

需求2:查询总成绩超过 150 的学生姓名和总分,但只统计及格科目(≥60)。

这里必须先用 WHERE 过滤不及格的成绩行,再分组求和,最后用 HAVING 过滤总分。

SELECT name, SUM(score) AS total_score
FROM students
WHERE score >= 60
GROUP BY name
HAVING total_score > 150;

如果错误地在 HAVING 里写 score >= 60,此时 score 已经不存在整行的概念,MySQL 会报错(或在某些模式下取随机值)。


总结对比卡

特性 WHERE HAVING
操作对象 原始表中的 分组聚合后的
执行时机 分组前(FROM 之后) 分组后(GROUP BY 之后)
能否用聚合函数 ❌ 不能 ✅ 必须(主要用途)
能否使用列别名 ❌ 通常不能 MySQL 中可以,但不推荐依赖
性能建议 先用它过滤行,缩小数据集 只放真正需要的聚合条件
搭配 不带 GROUP BY 时使用 通常与 GROUP BY 一起使用

下次写 SQL 之前,先问自己:这个条件依赖的是单行数据,还是分组计算后的结果? 答案立刻告诉你该把它放在 WHERE 还是 HAVING 里。