七个子句有两个顺序:
书写顺序是 select → from → where → group by → having → order by → limit,
执行顺序是 from → where → group by → having → select → order by → limit。
Demo 里把 where 写到 group by 后面,报 near "WHERE": syntax error;
执行顺序解释了为什么别名在 where 里用不了、在 having 和 order by 里能用。
最后一句七子句全上:剔掉已取消、归成 3 堆、筛掉已付款,留 2 行。
前面四篇各讲一个子句,这一篇把它们串成一条流水线。
这是全书唯一一篇「讲顺序」的,但它解释的现象会贯穿到最后一章:
别名能不能用、索引能不能吃上、having 为什么慢,都跟这个顺序有关。
一、书写顺序:七个位置不能换
select → from → where → group by → having → order by → limit。
这个顺序是语法规定,换一个位置就报错。
典型的错是把 where 写到 group by 后面——报错是 near "WHERE": syntax error。
看着像「这个词不认识」,其实是放错位置了。
另一个常见的错是把 order by 写到 limit 后面。 记住一个锚点:limit 永远是最后一个。
二、执行顺序:跟书写顺序不一样
from → where → group by → having → select → order by → limit。
注意 select 排在第五位,不是第一位。
| 子句 | 书写位次 | 执行位次 | 干什么 |
|---|---|---|---|
select |
1 | 5 | 挑列、算别名 |
from |
2 | 1 | 定表 |
where |
3 | 2 | 筛行 |
group by |
4 | 3 | 归堆 |
having |
5 | 4 | 筛堆 |
order by |
6 | 6 | 排序 |
limit |
7 | 7 | 截行 |
中间四行是错位的,这就是所有「别名怪现象」的根源。
order by 和 limit 位置没变,所以它们用别名一直没问题。
三、拿执行顺序解释三件事
一、别名在 where 里用不了。 where 是第 2 步,select 是第 5 步,
轮 where 的时候别名还不存在。
二、别名在 order by 里能用。 order by 是第 6 步,
排在 select 后面,别名已经算出来了。
三、别名在 having 里也能用(MySQL)。 按上面的表,having 是第 4 步,本该也用不了;
MySQL 额外做了别名解析,属于它比标准多给的一点方便。 换个数据库未必有,别把它当通用规则。
四、七个子句全上
from orders 定表 → where 剔掉已取消 → group by status 归成 3 堆 →
having money >= 150 筛掉已付款那堆 → select 算出 n 和 money →
order by 按金额排 → limit 3 留前三行。
这次 limit 3 只拿到 2 行——筛完只剩 2 堆。 limit 是「最多」,不是「必须」。
抠掉 where,
会多出「已取消 156.00」——它金额过了 150,having 拦不住它。 抠掉 having,
会多出「已付款 138.40」——它是正常的行,只是合计不够。
两句话记住:where 拦不住的行,having 有可能拦住;
having 拦不住的行(没进分组就出局的),只有 where 能拦。
五、本章练习提示
本书第 4 章末尾的练习,考的就是这一篇的顺序:给你一句顺序写错的 SQL,
让你调回能跑的样子,再说出它算的是什么。
做这类题的方法:先把七个位置按书写顺序摆好,再对着执行顺序逐句念一遍「这一步在干什么」。
念不顺的地方,就是顺序错了。
额外补一句:select 排在第五位这件事,
还解释了另一个常见疑问——为什么 where 里不能用聚合函数(第 3 步还没分组),
为什么 having 可以(第 4 步堆已经在了)。
六、动手 Demo:七个子句排成一列,再一个个抠掉
Demo 四段:先撞一次「where 写错位置」,再排七子句全上的语句,
最后分别抠掉 where 和 having 看谁拦住谁。
这一段没有新语法,全是把前四篇的零件按顺序拼起来。
import _q46 as Q
c = Q.con()
# 书写顺序:select from where group by having order by limit
c.execute("SELECT class_id, COUNT(*) FROM students "
"GROUP BY class_id WHERE class_id = 1") # 报错
c.execute("SELECT class_id, COUNT(*) AS n FROM students "
"WHERE class_id = 1 GROUP BY class_id")
# 七个子句全上
c.execute("SELECT status, COUNT(*) AS n, SUM(amount) AS money "
"FROM orders WHERE status <> '已取消' GROUP BY status "
"HAVING money >= 150 ORDER BY money DESC LIMIT 3")
# 抠掉 where:已取消那堆回来
c.execute("SELECT status, COUNT(*) AS n, SUM(amount) AS money "
"FROM orders GROUP BY status HAVING money >= 150 "
"ORDER BY money DESC")
# 抠掉 having:已付款那堆回来
c.execute("SELECT status, COUNT(*) AS n, SUM(amount) AS money "
"FROM orders WHERE status <> '已取消' "
"GROUP BY status ORDER BY money DESC")
实跑输出:
--------------------------------------------------------------------
第十九课:七个子句的顺序,以及为什么别名 where 用不了
--------------------------------------------------------------------
书写顺序:背这七个位置
select → from → where → group by → having → order by → limit
**这个顺序不能换。** 比如把 `where` 写到 `group by` 后面,直接语法错。
SQL> SELECT class_id, COUNT(*) FROM students GROUP BY class_id WHERE class_id = 1
报错:near "WHERE": syntax error
**报错指在 `WHERE` 上,`near "WHERE": syntax error`。**
**写错顺序的报错看着像「这个词不认识」,其实是你把它放错地方了。**
SQL> SELECT class_id, COUNT(*) AS n FROM students WHERE class_id = 1 GROUP BY class_id
class_id n
----------------------
1 4
(1 行)
**把 `where` 挪到 `group by` 前面,立刻就好了。**
执行顺序:跟书写顺序不一样
from → where → group by → having → select → order by → limit
**`from` 先定表,`where` 筛行,`group by` 归堆,`having` 筛堆。**
**然后才轮到 `select` 挑列算别名,最后 `order by` 排、`limit` 截。**
**这就是为什么 `where` 里用不了别名**——轮它的时候 `select` 还没跑,别名还不存在;
**而 `order by` 在 `select` 之后,所以能用。**
**`having` 也能用别名,属于引擎给的便利**(标准 SQL 里它也在 `select` 之前,严格说不该能)。
七个子句全上:一条语句跑完
SQL> SELECT status, COUNT(*) AS n, SUM(amount) AS money FROM orders WHERE status <> '已取消'
GROUP BY status HAVING money >= 150 ORDER BY money DESC LIMIT 3
status n money
-------------------------
已完成 8 1262.20
已发货 3 412.50
(2 行)
**`from orders` 定表** → **`where` 剔掉已取消** → **`group by status` 归成 3 堆**
→ **`having money >= 150` 筛掉「已付款」那堆** → **`select` 算出 `n` 和 `money`**
→ **`order by` 按金额排** → **`limit 3` 留前三行**。
**`limit 3` 这次只拿到 2 行**——剩下就 2 堆,`limit` 是「最多」,不是「必须」。
少一个子句,少的是什么
**上面那句分别抠掉 `where` 和 `having`,对比看谁拦住了谁。**
SQL> SELECT status, COUNT(*) AS n, SUM(amount) AS money FROM orders GROUP BY status
HAVING money >= 150 ORDER BY money DESC
status n money
-------------------------
已完成 8 1262.20
已发货 3 412.50
已取消 1 156.00
(3 行)
**抠掉 `where`:多出一行「已取消 156.00」。**
**它金额过了 150,`having` 拦不住它——能拦它的只有 `where`。**
SQL> SELECT status, COUNT(*) AS n, SUM(amount) AS money FROM orders WHERE status <> '已取消'
GROUP BY status ORDER BY money DESC
status n money
-------------------------
已完成 8 1262.20
已发货 3 412.50
已付款 2 138.40
(3 行)
**抠掉 `having`:多出一行「已付款 138.40」。**
**它是正常的行,只是这堆的合计不够 150——只能由 `having` 来筛。**
**两句话记住**:`where` 拦不住的行,`having` 有可能拦住;
`having` 拦不住的行(没进分组就出局的),只有 `where` 能拦。
第一段报错。 near "WHERE": syntax error——把 where 挪到 group by 前面就立刻好了,
第二句验证了这一点。
第二段七子句全上。 走完六步,剩 2 行:1262.20 和 412.50。
第三段抠掉 where。 多出「已取消 156.00」,3 行。
第四段抠掉 having。 多出「已付款 138.40」,也是 3 行,但不是同一行。
这两段是这一篇的重点:两个筛子拦的是两种不同的行,缺一个就漏一种。
30 秒实验
- 把
limit 3改成limit 1,出来的是哪一行?为什么是它? having money >= 150换成having count(*) >= 3,结果变成哪几行?order by money DESC换成order by n DESC,顺序会变吗?
常见误区
- 凭「读起来顺」写子句顺序。
where紧跟from是对的, 但把它写到group by后面就报语法错,报错信息还指得不明显。 - 以为书写顺序就是执行顺序。 差在
select:它排第五,所以别名在它前面的子句里都用不了。 - 把 MySQL 的
having别名便利当成通用规则。 标准 SQL 不保证,换库就报Unknown column。
自测三问(点开对答案)
1. 七个子句的书写顺序和执行顺序分别是什么? —— 书写:select from where group by having order by limit; 执行:from where group by having select order by limit。 2. 为什么别名在 `where` 里用不了、在 `order by` 里能用? —— `where` 是第 2 步、`select` 是第 5 步,那时别名还不存在; `order by` 是第 6 步,排在 `select` 之后。 3. `having` 和 `where` 各拦得住哪种行? —— `where` 拦分组前的单行;`having` 拦分组后的整堆。 过了金额门槛的「已取消」只有 `where` 拦得住,合计不够的「已付款」只有 `having` 拦得住。查询这套到这里齐了:挑列、筛行、排序、限量、去重、分组、筛组。
下一篇换个方向——like 模糊匹配、rand() 随机、子查询。
- ☐ (本篇完,下一篇 5.1 模糊查询:like)