🗄️ 第三章 操作数据库-SQL的使用方法
📄 一句话概括

HAVING 对【分完的堆】提条件——WHERE 筛行、HAVING 筛堆;两者执行顺序是本节的灵魂题。

📝 本节和前面几节的关系

3-18 学会分堆聚合;本节补最后一块拼图:【只留人数 2 以上的班】【只留平均分 80 以上的班】——条件写在堆上,WHERE 干不了,HAVING 上场。

一、为什么 WHERE 干不了这活

想查【人数在 2 人以上的班】:

-- 报错!
SELECT class, COUNT(*)
FROM students
WHERE COUNT(*) >= 2      -- ✗ WHERE 执行时还没分堆,COUNT(*) 不存在
GROUP BY class;

顺序问题:WHERE 在分堆之前跑——那时每一行还是一行,【每堆多少人】这个数还没出生,WHERE 没法判断。

HAVING 在分堆之后跑——它看的正是【堆的聚合值】:

SELECT class, COUNT(*)
FROM students
GROUP BY class
HAVING COUNT(*) >= 2;
class  COUNT(*)
1      2
2      2

3 班(1 人)被 HAVING 滤掉——筛的是堆,不是行。

二、WHERE 与 HAVING 的分工表

WHERE HAVING
筛什么 行(分堆前) 堆(分堆后)
能用聚合函数吗 ✗(还没聚合) ✓(这正是它的用途)
位置 GROUP BY 前 GROUP BY 后

两者可以同时上场:

-- 有分数的行 → 按班分堆 → 只留平均分 70 以上的班
SELECT class, AVG(score) AS 平均
FROM students
WHERE score IS NOT NULL
GROUP BY class
HAVING AVG(score) >= 70;

执行流水线:先筛行(钱七出局)→ 分堆 → 每堆聚合 → 筛堆(3 班出局)。

三、动手 Demo:完整的查询流水线

-- 准备块(没建表先跑 3-6 的准备块)

-- ① 筛堆:人数 2 以上的班
SELECT class, COUNT(*) AS 人数
FROM students GROUP BY class HAVING COUNT(*) >= 2;

-- ② 筛堆:平均分 70 以上的班
SELECT class, AVG(score) AS 平均 FROM students GROUP BY class HAVING AVG(score) >= 70;

-- ③ WHERE + HAVING 连打
SELECT class, COUNT(*) AS 人数
FROM students
WHERE score IS NOT NULL
GROUP BY class
HAVING COUNT(*) >= 1;

②的输出:1 班 88.5、2 班 71.0(3 班无平均值被滤)。 ③的输出:1 班 2 人、2 班 2 人(3 班钱七被 WHERE 先筛走,堆里 0 人被 HAVING 滤掉)。

看到什么算成功:能对着③说出流水线四步【筛行→分堆→聚合→筛堆】各干掉了谁。

四、30 秒实验

把 3-18 30 秒实验的【销量前 10】升级成真业务:【销量 2 以上的班级再按人数排】:

SELECT class AS 商品, COUNT(*) AS 销量
FROM students
GROUP BY class
HAVING COUNT(*) >= 2
ORDER BY 销量 DESC;

——WHERE 筛行 → GROUP BY 分堆 → HAVING 筛堆 → ORDER BY 排队,查询四段论齐了。

五、常见误区

  1. "HAVING 和 WHERE 差不多,随便用" —— 完全两码事:WHERE 筛行、HAVING 筛堆;条件涉及聚合值(COUNT/AVG…)只能 HAVING。
  2. "WHERE 里能写 COUNT(*)" —— 报错:WHERE 执行时聚合还没发生。
  3. "能用 HAVING 的都用 HAVING" —— 反了:能放 WHERE 的条件放 WHERE(分堆前先筛掉,少算很多数据);HAVING 只收【必须看聚合值】的条件。
自测三问(点开对答案) 1. WHERE 和 HAVING 各筛什么? **WHERE 筛行(分堆前)、HAVING 筛堆(分堆后)**。 2. 为什么 WHERE 里不能写 COUNT(*)? **WHERE 先于分堆聚合执行,那时聚合值不存在**。 3. 一条查询的完整执行顺序? **WHERE 筛行 → GROUP BY 分堆 → HAVING 筛堆 → ORDER BY 排队**。

相关笔记