🦏 第4章 数据统计
📄 一句话概括

七个子句有两个顺序: 书写顺序是 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 秒实验

  1. 把 limit 3 改成 limit 1,出来的是哪一行?为什么是它?
  2. having money >= 150 换成 having count(*) >= 3,结果变成哪几行?
  3. order by money DESC 换成 order by n DESC,顺序会变吗?

常见误区

  1. 凭「读起来顺」写子句顺序。 where 紧跟 from 是对的, 但把它写到 group by 后面就报语法错,报错信息还指得不明显。
  2. 以为书写顺序就是执行顺序。 差在 select:它排第五,所以别名在它前面的子句里都用不了。
  3. 把 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() 随机、子查询。

相关笔记