where 筛行,having 筛组。 一句话分清楚:条件里出现聚合函数,就必须写 having。
Demo 里 4 堆订单加 having n > 2 变 2 堆;
同一个条件写进 where 会报 misuse of aggregate。
having 能用 select 的别名,where 不能;
两个一起用时是「先筛行 → 归堆 → 再筛组」,14 行变 2 堆。
上一篇把行归成了堆,这一篇决定哪些堆留下。
where 和 having 最容易混,因为两者都写条件、都用运算符。
分界线只有一条:看条件里有没有聚合函数。
一、分界线:条件里有没有聚合函数
where 跑在分组之前,那时还没有「堆」,所以它只认单行的列。 having 跑在分组之后,
堆已经在了,所以它能认 count(*)。
判断方法简单到不用想:条件里出现 count/sum/avg/max/min,就是 having;
全是单行能判断的条件,就是 where。
where count(*) > 2 必报错——Demo 里给的是 misuse of aggregate。
不是语法错,是「这时候还没堆可数」。
二、having 能用别名,where 不能
having n > 2 和 having count(*) > 2 结果一样,
因为 having 执行时 select 那一步算的别名已经在了。
这一点在第 3 章撞过:where double_h > 340 在 MySQL 上报 Unknown column。
同一个别名,having 和 order by 能用,where 不能用。
不过这里有个反例值得记住:按执行顺序,select 排在 having 后面,
严格说 having 也拿不到别名。 MySQL 额外做了别名解析,所以它认;
标准 SQL 不保证。 跨引擎的代码,having 里写聚合函数本身最稳。
三、两个一起用:先筛行,再筛组
where status <> '已取消' group by status having money > 200 分三步:
剔掉已取消(14 行变 13 行)→ 归成 3 堆 → 筛掉金额不到 200 的那堆。
顺序不能反,而且两个筛子拦的东西不一样:
where 拦不住「已取消 156.00」这种过了金额门槛的堆;
having 拦不住「已付款 138.40」这种单行正常、只是合计不够的堆。
所以「先 where 后 having」不只是语法规定,是逻辑必需:能早点剔掉的行,越早剔越好。
四、能写进 where 的都写进 where
having status = '已完成' 能给对结果,但白分组了:先把 14 行归成 4 堆,再扔掉 3 堆。
改成 where status = '已完成' 是先筛成 8 行再一次归堆,
参与分组的行少了,排序和临时表都省了。
规矩:筛「行」用 where,筛「组的计算结果」用 having。
凡是用 having 能写的、where 也能写的条件,一律挪到 where。
五、动手 Demo:从 4 堆筛到 2 堆,再撞一次报错
Demo 六段:先看 4 堆,加 having 筛成 2 堆,再换成聚合函数写法。
第四段故意把聚合塞进 where,看它报什么错;
后两段是两个筛子一起用,和 having 筛分组列的浪费写法。
import _q46 as Q
c = Q.con()
# 没有 having:四堆全靠
c.execute("SELECT status, COUNT(*) AS n FROM orders GROUP BY status "
"ORDER BY n DESC")
# 加 having:只留 3 张以上的堆
c.execute("SELECT status, COUNT(*) AS n FROM orders GROUP BY status "
"HAVING n > 2 ORDER BY n DESC")
# 写成聚合函数也行
c.execute("SELECT status, COUNT(*) AS n FROM orders GROUP BY status "
"HAVING COUNT(*) > 2")
# where 里放聚合:报错
c.execute("SELECT status, COUNT(*) FROM orders WHERE COUNT(*) > 2 "
"GROUP BY status")
# 两个一起用:先筛行再筛组
c.execute("SELECT status, COUNT(*) AS n, SUM(amount) AS money "
"FROM orders WHERE status <> '已取消' GROUP BY status "
"HAVING money > 200 ORDER BY money DESC")
# having 筛分组列:能过,但白分组
c.execute("SELECT status, COUNT(*) AS n FROM orders GROUP BY status "
"HAVING status = '已完成'")
实跑输出:
--------------------------------------------------------------------
第十八课:having,分组之后再筛一遍
--------------------------------------------------------------------
先看没有 having 时的四堆
SQL> SELECT status, COUNT(*) AS n FROM orders GROUP BY status ORDER BY n DESC
status n
------------------
已完成 8
已发货 3
已付款 2
已取消 1
(4 行)
**四堆:8、3、2、1。** 现在想只留「3 张以上」的堆。
加 having:筛掉整堆
SQL> SELECT status, COUNT(*) AS n FROM orders GROUP BY status HAVING n > 2 ORDER BY n DESC
status n
------------------
已完成 8
已发货 3
(2 行)
**从 4 行变 2 行。`having n > 2` 说的是「这一堆的行数要大于 2」,不满足的整堆被丢掉。**
having 里能用别名,也能直接写聚合
SQL> SELECT status, COUNT(*) AS n FROM orders GROUP BY status HAVING COUNT(*) > 2
status n
------------------
已发货 3
已完成 8
(2 行)
**`having n > 2` 和 `having count(*) > 2` 结果一样。**
**MySQL 允许 `having` 用 `select` 里的别名,`where` 不允许**——下一节用执行顺序解释这件事。
where 里不能放聚合:这是两者的分界线
SQL> SELECT status, COUNT(*) FROM orders WHERE COUNT(*) > 2 GROUP BY status
报错:misuse of aggregate: COUNT()
**`where` 跑的时候还没分组,`count(*)` 根本无从算起,所以这条一定报错。**
**判据只有一句:条件里出现聚合函数,就必须写 `having`。**
两个一起用:先筛行,再筛组
SQL> SELECT status, COUNT(*) AS n, SUM(amount) AS money FROM orders WHERE status <> '已取消'
GROUP BY status HAVING money > 200 ORDER BY money DESC
status n money
-------------------------
已完成 8 1262.20
已发货 3 412.50
(2 行)
**`where` 先把「已取消」那行剔掉(14 行变 13 行)**,`group by` 再把剩下的归成 3 堆。
**`having` 最后筛掉金额不到 200 的那堆。** **三行变两行,走掉的是「已付款 138.40」。**
**顺序不能反:`where` 管行,`group by` 管堆,`having` 管堆的去留。**
having 筛分组列也行,但通常该用 where
SQL> SELECT status, COUNT(*) AS n FROM orders GROUP BY status HAVING status = '已完成'
status n
------------------
已完成 8
(1 行)
**能给对结果,但白分组了**——先把 14 行归成 4 堆再扔掉 3 堆,
**不如 `where status = '已完成'` 先筛成 8 行再一次归堆。**
**规矩:筛「行」用 `where`,筛「组的计算结果」用 `having`。**
**能写进 `where` 的都写进 `where`。**
第一段 4 堆。 8、3、2、1。
第二段筛成 2 堆。 having n > 2 只留已完成和已发货。
第三段换成 having count(*) > 2。 结果一模一样,但输出顺序反了——having 里没了别名,
优化器走了另一条路。
第四段报错。 misuse of aggregate: COUNT(),说明 where 执行时聚合还没算。
第五段两个筛子。 3 行变 2 堆,走掉的是已付款 138.40。
第六段 having status = '已完成'。 给 1 行,结果对,但白分了 4 堆。
30 秒实验
- 把
having n > 2改成having n >= 2,会出几行? HAVING COUNT(*) > 2与WHERE COUNT(*) > 2差别在哪一步?having money > 200里的 200 换成 150,结果会多几行?
常见误区
- 把聚合条件写进
where。 报misuse of aggregate,而且报错位置在where上, 容易以为是写错了函数名。 - 该用
where的用成having。 结果正确,但分组和排序白做了一遍,数据量大时能差出几倍时间。 - 依赖
having用别名写跨引擎代码。 MySQL 认,标准 SQL 不保证,换个库就报Unknown column。
自测三问(点开对答案)
1. `where` 和 `having` 的分界线是什么? —— 条件里有没有聚合函数。有就是 `having`,没有就用 `where`。 2. 为什么 `where count(*) > 2` 一定报错? —— `where` 在分组之前执行,那时还没有「堆」可以数,聚合无从算起。 3. 两个一起用时谁先谁后?为什么? —— `where` 先、`having` 后。先剔掉不要的行,参与分组的行就少,分组和排序都省。到这里 select/where/group by/having/order by/limit 都见过了。
下一篇把七个位置一次排清楚——书写顺序和执行顺序还不是一回事。
- ☐ (本篇完,下一篇 4.5 子句顺序)