SELECT 的六段里,只有第一段不能省,其余五段都是可选的。
书写的顺序不是执行的顺序:执行从 FROM 开始,SELECT 排在第五位。
沙箱里跑通不等于 MySQL 里跑通,WHERE 用别名就是一例。
第 8 章把数据写进去了,这一章开始读。9.1 是全书最长一章的开头,只讲语法骨架。 第 9 章一共八篇,一个节一篇。 这里出现的每个细节,后面几篇都会反复用到。
一、六段各管什么
教材 9.1 给的语法骨架有六段,但真正必需的只有第一段。
| 子句 | 管什么 | 能不能省 |
|---|---|---|
SELECT |
查哪些列 | 不能 |
FROM |
从哪张表 | 可以,SELECT 1+1 |
WHERE |
筛行 | 可以,省了就是全部 |
GROUP BY |
分组 | 可以 |
HAVING |
筛组 | 可以 |
ORDER BY |
排序 | 可以 |
LIMIT |
取几条 | 可以 |
不写 WHERE 就是全表,不写 ORDER BY 就是「数据库给的顺序」。
注意那不是随机,是「没保证」——同一句查两次,结果顺序可能不同。
第二行和第三行最容易混:WHERE 在分组之前筛行,HAVING 在分组之后筛组。
所以 WHERE 里不能写 COUNT(*),那时还没分组,没有「一组多少行」这回事。
二、书写顺序与执行顺序
教材给了一条完整的查询语句,看着是从上到下写的。
SELECT cat_id, COUNT(*) AS n, AVG(price) AS avg_price
FROM tb_goods
WHERE price > 10
GROUP BY cat_id
HAVING n > 300
ORDER BY n DESC
LIMIT 3;
但数据库不是从上往下执行的,它从 FROM 开始。
中间依次是 WHERE、GROUP BY、HAVING,最后才轮到 SELECT、ORDER BY、LIMIT。
这件事有一个可以验证的后果:SELECT 里起的别名,在 WHERE 里不该能用。
因为执行到 WHERE 时 SELECT 还没跑,别名根本还不存在。
但沙箱里的结果打了脸:WHERE、ORDER BY、HAVING 三处都能用这个别名。
原因是 SQLite 把标准放宽了,允许在 WHERE 里直接引用输出别名。
MySQL 不放宽,WHERE 里用别名会报 Unknown column 'p' in 'where clause'。
所以这条规矩不能拿别名来证,得换一个两个引擎都不放宽的东西——聚合函数。
把 COUNT(*) 放进 WHERE 会直接报 misuse of aggregate: COUNT(),放进 HAVING 却正常。
两个引擎在这一点上完全一致,这才是执行顺序的硬证据。
记住一句话就够了:WHERE 筛的是「行」,那时还没有分组,也还没有别名。
三、SELECT * 的代价,和没有 FROM 的 SELECT
SELECT * 不只是「方便」,它是「把每一列都取出来」。
这张表有八列,只取两列和取全部,耗时差了一截。
差得不算夸张,是因为这张表没有大字段。
真有一列 TEXT 存几千字,SELECT * 会比只取两列慢一个数量级。
所以「别写 SELECT *」有两个实在的理由:一是省传输,二是表结构变了以后,
依赖列顺序的代码不会莫名其妙错位。
另一个常被忽略的用法:SELECT 可以不带 FROM,这时它就是一个计算器。
想验一个函数、一个表达式怎么算,不用建表,直接 SELECT 出来看。
SELECT 10 / 4 在沙箱里给 2,在 MySQL 里给 2.5000。
所以「验算式」这件事本身也要带着引擎的差异看,别把它当成标准答案。
四、动手 Demo:一条 SELECT 的六段,与它真正的执行顺序
下面这段把三件事真跑一遍:六段各跑一次、别名放在三个位置各试一次、
再把聚合函数放在 WHERE 和 HAVING 里各试一次。
import sqlite3
c = sqlite3.connect(u"d036.db")
c.execute(u"CREATE TABLE tb_goods (id INTEGER PRIMARY KEY, "
u"code TEXT, name TEXT, price REAL, cat_id INTEGER)")
# ① 别名放在三个位置
for sql in [u"SELECT price AS p FROM tb_goods WHERE p > 100",
u"SELECT price AS p FROM tb_goods ORDER BY p DESC",
u"SELECT cat_id, MAX(price) AS p FROM tb_goods "
u"GROUP BY cat_id HAVING p > 100"]:
try:
c.execute(sql).fetchall()
print(u"能用", sql)
except sqlite3.OperationalError as e:
print(u"报错", e)
# ② 聚合函数放在 WHERE 和 HAVING 里
c.execute(u"SELECT cat_id, COUNT(*) FROM tb_goods "
u"WHERE COUNT(*) > 1 GROUP BY cat_id") # 报错
c.execute(u"SELECT cat_id, COUNT(*) FROM tb_goods "
u"GROUP BY cat_id HAVING COUNT(*) > 1") # 正常
# ③ 没有 FROM 的 SELECT
print(c.execute(u"SELECT 10 / 4").fetchone()) # (2,)
实跑输出:
--------------------------------------------------------------------
第三十六课:一条 SELECT 的六段,与它真正的执行顺序
--------------------------------------------------------------------
**教材 9.1 讲基本查询语句。`SELECT` 的语法有六段,能省的只有第一段。**
**但更要紧的是另一件事:书写的顺序和执行顺序不一样。**
**这一课先把六段认全,再用「别名能不能用」这件事把执行顺序验证出来。**
一、六段各管什么
--------------------------------------------------------------------
子句 管什么 能不能省
------------------------------------------
`SELECT` 查哪些列 不能
`FROM` 从哪张表 可以(`SELECT 1+1`)
`WHERE` 筛行 可以,省了就是全部
`GROUP BY` 分组 可以
`HAVING` 筛组 可以
`ORDER BY` 排序 可以
`LIMIT` 取几条 可以
**七行里第一行和第二行是必需的,其余五行都可以不写。**
**不写 `WHERE` 就是全表,不写 `ORDER BY` 就是「数据库给的顺序」——注意那不是随机,是没保证。**
**第三行「筛行」和第五行「筛组」很容易混。**
**`WHERE` 在分组之前筛、`HAVING` 在分组之后筛。**
**所以 `WHERE` 里不能写 `COUNT(*)`,那时还没分组。**
二、书写顺序和执行顺序不是一回事
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| SELECT cat_id, COUNT(*) AS n, AVG(price) AS avg_price
| FROM tb_goods
| WHERE price > 10
| GROUP BY cat_id
| HAVING n > 300
| ORDER BY n DESC
| LIMIT 3;
① 教材原码,本沙箱不执行
**实际执行顺序不是从上到下,而是从 `FROM` 开始。**
**中间依次是 `WHERE`、`GROUP BY`、`HAVING`。**
**最后才轮到 `SELECT`、`ORDER BY`、`LIMIT`,所以 `SELECT` 排在第五位。**
**有一条老规矩可以用来验这件事:`SELECT` 里起的别名,在 `WHERE` 里不该能用。**
**理由是执行到 `WHERE` 时 `SELECT` 还没跑,别名还不存在。**
别名放在哪 本沙箱的结果
------------------------------
`WHERE p > 100` 能用
`ORDER BY p` 能用
`HAVING p > 100` 能用
**三处都能用,所以这条规矩在沙箱里证不出来。**
**原因是 SQLite 把标准放宽了:它允许在 `WHERE` 里直接引用输出别名。**
**MySQL 不放宽,`WHERE` 里用别名会报 `Unknown column 'p' in 'where clause'`。**
**这一条要单独记住:沙箱里跑通,不等于 MySQL 里跑通。**
聚合函数放在哪 本沙箱的结果
---------------------------------------------------------
`WHERE COUNT(*) > 1` 报错:misuse of aggregate: COUNT()
`HAVING COUNT(*) > 1` 成功,11 行
**这一组才是执行顺序的真证据。**
**聚合函数只能在分组之后算,而 `WHERE` 排在分组之前,所以直接把函数用在那里会报错。**
**两个引擎在这一点上一致,不像别名那条只有 MySQL 卡。**
**所以用聚合函数验执行顺序,比用别名可靠。**
**顺带说清 MySQL 的一处放宽:`HAVING` 里能用别名,标准 SQL 不保证。**
**写 SQL 时别依赖它,换个库就可能报错。**
三、SELECT * 的代价
--------------------------------------------------------------------
**`SELECT *` 不是「查全部列」这么简单,它是「把每一列都取出来」。**
**列里如果有大字段(`TEXT`、`BLOB`),代价立刻上来。**
写法 取回多少行 耗时
-----------------------------------------
`SELECT *` 5000 12.49 毫秒
`SELECT id, name` 5000 5.31 毫秒
**两行行数一样,耗时差了一截。**
**因为 `SELECT *` 要组装的列更多,网络和内存的负担都更重。**
**这一格差得不算大,是因为这张表没有大字段。**
**真有一列 `TEXT` 存几千字,`SELECT *` 会比只取两列慢一个数量级。**
**所以「别写 `SELECT *`」这条规矩不是洁癖,它有两个实在的理由:**
**一是省传输,二是表结构变了以后,依赖列顺序的代码不会莫名其妙错位。**
四、没有 FROM 的 SELECT
--------------------------------------------------------------------
**`SELECT` 可以不带 `FROM`,这时它就是一个计算器。**
写法 结果
-----------------------------
`SELECT 1 + 1` 2
`SELECT UPPER('abc')` ABC
`SELECT LENGTH('中文')` 2
`SELECT 10 / 4` 2
**这个用法在调试时很有用:想验一个函数、一个表达式怎么算,不用建表。**
**第 7 章那些运算符的对照,都是这么跑出来的。**
**注意最后一行:`10 / 4` 给 2。**
**MySQL 会给 2.5000,这是第 7 章讲过的除法分叉。**
**所以「验算式」这件事本身也要带着引擎的差异看。**
输出里最值得看的是第二组那两行。
别名那一组三行全「能用」,说明沙箱把标准放宽了,验不出执行顺序。
聚合函数那一组才是真证据:WHERE COUNT(*) 直接报 misuse of aggregate,
HAVING COUNT(*) 给出 11 行。
这 11 行也顺便说明了 HAVING 在干什么:它是在分组之后把整组筛掉,而不是筛掉组里的某一行。
最后那个 10 / 4 给 2,是整数除法的结果。
同一句在 MySQL 上给 2.5000,这个分叉在第 7 章讲过,这里再撞一次。
30 秒实验
- 把
SELECT 1 + 1换成SELECT 7 / 2,看沙箱给 3 还是 3.5,再想想 MySQL 会给什么。 - 把
SELECT *换成只取两列,各跑三遍取最快的那次,看差多少倍。 - 试
SELECT NULL = NULL,看它给什么——这个结果就是三值逻辑的起点。
常见误区
- 以为书写顺序就是执行顺序,于是在
WHERE里用SELECT起的别名。 - 以为
SELECT *只是「少打几个字」,没意识到它把大字段也一起搬了。 - 以为沙箱跑通就等于 MySQL 跑通,把「别名能用」这种放宽当成了标准行为。
自测三问(点开对答案)
1. 六段里哪一段不能省?`FROM` 为什么能省? —— `SELECT` 不能省。 `FROM` 能省是因为可以查「常量」,比如 `SELECT 1 + 1`,这时没有表可查,结果只有一行。 2. 为什么 `WHERE` 里不能写 `COUNT(*)`? —— 因为 `WHERE` 在分组之前执行,那时还没有「组」,`COUNT(*)` 要数的东西还不存在。 分组之后的 `HAVING` 才能用它。 3. `SELECT *` 的两个害处是什么? —— 一是把不需要的列(可能含大字段)也搬出来,白花传输和内存; 二是依赖列顺序的代码在表结构变动后会错位。六段认全了,下一篇进 9.2:单表查询里的五类条件,和三个真会出事的坑。
- ☐ (本篇完,下一篇 9.2 单表查询)
相关笔记
- 运算符
- 添加数据
-
- 🐬 MySQL必知必会/00-索引|45. 🐬 MySQL必知必会
- 🛢️ MySQL从入门到精通 · 索引