SELECT 的骨架只有两个词: SELECT(要什么列)加 FROM(从哪张表)。这一章把这副骨架拆成六种用法:单列、多列、所有列、 去掉重复、只取几行、写全表名。 Demo 在 13 行产品表上跑了一遍,几个关键数: 星号一次拿回 13 行乘 5 列共 65 个值;DISTINCT 去重后 4 个供货商; 两列一起 DISTINCT 却剩 11 行;LIMIT 5 OFFSET 5 的第一行是 Carrots;OFFSET 20 直接给 0 行。
上一章摸清了地形,这一章开始取数。 全书 30 章里有 12 章在讲「怎么把数取出来」,这是第一层:只挑列,不筛行、不排序。 三个新词先记住:DISTINCT(去重)、LIMIT(限行)、OFFSET(跳行)。它们在 MySQL 里能用, 在别的数据库里名字可能不同(SQL Server 用 TOP, Oracle 老版本用 ROWNUM)——方言就从这里开始分岔。
一、SELECT 语句的骨架
最小的 SELECT 长这样:SELECT 列名 FROM 表名; 两个词,缺一不可。
SELECT 后面是要哪些列,FROM 后面是从哪张表。 中间用空格,列与列之间用逗号,最后用分号收尾。
一条 SELECT 的执行顺序和书写顺序不一样。
书写是 SELECT…FROM…WHERE…GROUP BY…HAVING…ORDER BY…LIMIT;
但数据库内部的逻辑顺序是 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。
(SELECT 排在中间靠后,所以 WHERE 看不见别名,ORDER BY 才看得见——第 13 章细讲。)
这一条解释了后面很多「为什么不行」:WHERE 里不能用 SELECT 里刚起的别名,但 ORDER BY 里可以;
HAVING 能用 COUNT(),WHERE 不能。记住了顺序,这些就不是死记硬背。
二、检索单列、多列、所有列
单列:SELECT prod_name FROM products; 返回 13 行,一列。
注意返回的行序没有任何保证——你现在看到 ANV01 在前,那是巧合。
要顺序就得自己写 ORDER BY(第 5 章)。
多列:把要的列用逗号连起来,比如取编号、名字、价格三个。
逗号是列分隔符,最后一个列后面不能多写逗号。 SELECT a, b, FROM t 是语法错,
这是新手第一个高频报错。
所有列:SELECT * FROM products; 星号代表「全部列」。
Demo 里它一次拿回 13 行乘 5 列共 65 个值。
星号有两个代价。 一是把不需要的列也传过来——大表上这一下可能是几兆流量;
二是别人给表加一列,你的结果就悄悄多一列,按位置取值的程序从此错位,而且不报错。
所以除了「临时看一眼」,生产代码里把列名写全。
这里有个容易搞混的地方:SELECT * 的列顺序是建表时的顺序,不是字母序,
也不是你在别处看到的顺序。顺序不保证,靠位置取值是危险的。
三、检索不同的行:DISTINCT
products 表 13 行,但只有 4 个供货商。
直接查 vend_id 会看到一堆重复——SELECT DISTINCT vend_id FROM products; 只留 4 行。
DISTINCT 后面跟几个列,就作用于几个列的组合,不是只作用于第一列。
Demo 里 DISTINCT vend_id, prod_price 得到 11 行,
不是 4 行——因为同一供货商的不同价格也算不同的组合。
这一条是 DISTINCT 最常见的误解。 想要「每个供货商一行」,就只写 vend_id 一个列;
写了两个列,就是「每一种组合一行」。
DISTINCT 不能只作用于部分列。 写 SELECT DISTINCT vend_id, prod_price 不等于「vend_id 去重、
prod_price 照旧」——它没法这么干,因为去重之后每一行都是一组值。
DISTINCT 有代价。 数据库得先排序或建哈希表才能去重,大表上这一步很贵。
能确定不重复的地方就别加 DISTINCT。
四、限制结果:LIMIT 与 OFFSET
LIMIT 5 要前 5 行。 单给 LIMIT 就是「从头数 5 行」。
LIMIT 5 OFFSET 5 是「跳过前 5 行,再取 5 行」,也就是第 6 到第 10 行。
Demo 里这一句的第一行是 Carrots,正是第 6 行。
翻页公式:OFFSET =(页码 − 1)× 每页条数。 第 3 页、每页 5 条,就是 LIMIT 5 OFFSET 10。
越过末尾不报错,返回 0 行。 Demo 里 OFFSET 20 就是这种情况——表里只有 13 行,
跳过 20 行自然什么都没有。很多人以为这是「查错了」,其实是正常结果。
LIMIT 必须配 ORDER BY,否则翻页会重复或漏行。 因为不排序时行的顺序不保证,
第 1 页和第 2 页可能给出同一行、也可能漏掉一行。这是分页 bug 的头号来源。
教材还提了 MySQL 的简写:LIMIT 4, 5 等于 LIMIT 5 OFFSET 4,逗号前面是偏移、后面是条数。
和标准写法顺序相反,容易记错,建议一律写 OFFSET。
五、使用完全限定的表名
SELECT products.prod_name FROM products; 列名前面加上表名,这叫完全限定。
只查一张表时它是废话,但有两个场合必须写。
一是多表联结时列名撞车。 第 15 章一联结,products 和 orderitems 都可能出现 prod_id,
不写表名数据库就不知道你说的是哪个,直接报 ambiguous column。
二是表名本身要限定。 MySQL 里可以写三层:库名.表名.列名,
比如 crashcourse.products.prod_name。跨库查询时这一层是必需的,不然数据库不知道去哪个库找。
写表名还有个别的好处:可读性。 一段几十行的 SQL,
看到 o.order_num 就知道这个字段来自 orders 表,不用回头翻 FROM 那一行。
六、小结
教材的小结很短:SELECT 拿列,DISTINCT 去重,LIMIT 限行。 本课补三条:一是行序不保证,要顺序必须写 ORDER BY; 二是 DISTINCT 作用于它后面所有列的组合,不是只作用于第一列; 三是 LIMIT 翻页必须配 ORDER BY,否则会重复或漏行。
七、动手 Demo:六种取法,一层一层加上去
这段代码按教材 4.1~4.7 的顺序走一遍:单列、多列、星号、去重、限行、完全限定。 每一步都打出真实的 SQL 和结果,行数写在括号里。 看得时候盯两个数:13 行怎么变成 4 行(DISTINCT),又怎么变成 11 行(两列 DISTINCT)。
import sqlite3
con = sqlite3.connect(":memory:") # 这里换成打开样例库
con.execute("SELECT prod_name FROM products") # 单列
con.execute("SELECT prod_id, prod_name, prod_price FROM products") # 多列
con.execute("SELECT * FROM products") # 所有列
con.execute("SELECT DISTINCT vend_id FROM products") # 去重
con.execute("SELECT prod_name FROM products LIMIT 5 OFFSET 5") # 第 6~10 行
con.execute("SELECT products.prod_name FROM products") # 完全限定
实跑输出:
--------------------------------------------------------------------
第四课:检索数据
--------------------------------------------------------------------
【一】检索单个列
SQL> SELECT prod_name FROM products
prod_name
---------------
.5 ton anvil
1 ton anvil
2 ton anvil
Detonator
Bird seed
Carrots
Fuses
JetPack 2000
Oil can
Safe
Sling
TNT (1 stick)
TNT (5 sticks)
(13 行)
**一条 SQL 至少要有两样:SELECT(要什么)和 FROM(从哪张表)。**
**返回的顺序没有任何保证**——你现在看到的是 ANV01 打头,那是巧合,不是规则。
【二】检索多个列:列名之间用逗号
SQL> SELECT prod_id, prod_name, prod_price FROM products LIMIT 5
prod_id prod_name prod_price
----------------------------------------
ANV01 .5 ton anvil 5.99
ANV02 1 ton anvil 9.99
ANV03 2 ton anvil 14.99
DTNTR Detonator 13.00
FB Bird seed 10.00
(5 行)
**逗号是列的分隔符,最后一个列后面不能多写逗号。**
这是新手第一个高频报错:`SELECT a, b, FROM t` 会报语法错。
【三】检索所有列:星号的代价
SELECT * 返回 13 行 × 5 列 = 65 个值
列依次是:prod_id、vend_id、prod_name、prod_price、prod_desc
**星号省事,但有两个代价:一是把不需要的列传过来(网络与内存白花),
二是别人给表加一列,你的结果就悄悄多一列,程序可能就此错位。**
**所以除了「临时看一眼」,生产代码里尽量把列名写全。**
【四】检索不同的行:DISTINCT
SQL> SELECT vend_id FROM products
vend_id
--------
1001
1001
1001
1003
1003
1003
1002
1005
1002
1001
1003
1003
1003
(13 行)
13 个产品只来自 4 个供货商,上面这一列里有大量重复。
SQL> SELECT DISTINCT vend_id FROM products
vend_id
--------
1001
1003
1002
1005
(4 行)
**DISTINCT 作用于它后面所有的列,不是只作用于第一列。**
SQL> SELECT DISTINCT vend_id, prod_price FROM products LIMIT 6
vend_id prod_price
-----------------------
1001 5.99
1001 9.99
1001 14.99
1003 13.00
1003 10.00
1003 2.50
(6 行)
两列一起看:只要组合不同就算不同行——13 个产品去重之后还剩 11 行,
**比只看 vend_id 时的 4 行多了整整 7 行,这就是「DISTINCT 作用于全部列」的意思。**
【五】限制结果:LIMIT 与 OFFSET
SQL> SELECT prod_name FROM products LIMIT 5
prod_name
-------------
.5 ton anvil
1 ton anvil
2 ton anvil
Detonator
Bird seed
(5 行)
**LIMIT 5 要 5 行,但没说从第几行开始。** 单给 LIMIT,就是「前 5 行」。
SQL> SELECT prod_name FROM products LIMIT 5 OFFSET 5
prod_name
-------------
Carrots
Fuses
JetPack 2000
Oil can
Safe
(5 行)
**OFFSET 5 是「跳过前 5 行」**,所以这一批是第 6 到第 10 行。
翻页公式:**OFFSET = (页码 − 1) × 每页条数**。第 3 页、每页 5 条,就是 OFFSET 10。
SQL> SELECT prod_name FROM products LIMIT 5 OFFSET 20
prod_name
----------
(0 行)
**越过末尾不报错,返回 0 行。** 这一点常被误解成「查错了」。
【六】使用完全限定的表名
SQL> SELECT products.prod_name FROM products LIMIT 3
prod_name
-------------
.5 ton anvil
1 ton anvil
2 ton anvil
(3 行)
**表名.列名**。
只查一张表时这是废话,但第 15 章一联结,两张表都有 prod_name 这种列时,
不写表名数据库就不知道你说的是谁。
MySQL 里还能写全三层:**库名.表名.列名**,比如 `crashcourse.products.prod_name`。
第一段是「最基本的一条 SQL」:一个列、一张表、13 行。 注意它没有 ORDER BY,所以这 13 行的顺序是数据库自己决定的。 第二段加了一个逗号,就变成三列。 五列的 65 个值在这里变成 5 行乘 3 列——因为后面加了 LIMIT 5。 LIMIT 不改变列,只砍行。 第三段星号那两句是这章最实用的提醒。 65 个值一次传过来,而你真正常用的可能只有两列。 大表上这不是小事。 第四段是这一章的重点。 先看 13 行里 vend_id 的重复(1001 出现四次、1003 六次), 再看 DISTINCT 之后只剩 4 行。 然后两列一起 DISTINCT,变成 11 行——这一跳最能说明「DISTINCT 作用于全部列」。 第五段 LIMIT 与 OFFSET。 三个结果要对照着看:前 5 行、第 6 到第 10 行、 以及 OFFSET 20 的空结果。「越过末尾返回 0 行」不是错误,是正常。 第六段完全限定。 现在看它是啰嗦,等到第 15 章联结时回头看, 就知道为什么教材在讲联结之前先把这一节讲掉。
30 秒实验
- 把 LIMIT 5 OFFSET 10 换成 LIMIT 10 OFFSET 5,结果的行数和内容各变成什么?
- 试
SELECT DISTINCT vend_id, prod_id FROM products;,返回几行?为什么不是 4 行也不是 11 行? - 把
SELECT prod_name FROM products LIMIT 5连跑三次,行序每次都一样吗?如果不一致,说明什么?
常见误区
- 以为不排序也有固定顺序。小表上「每次都一样」纯属巧合。 要顺序就写 ORDER BY,哪怕你觉得「按主键自然是升序」。
- 以为 DISTINCT 只作用于第一列。
DISTINCT a, b是「a 和 b 的组合去重」。 只想去重一个列就只写一个列。 - 分页不写 ORDER BY。结果会重复或漏行,而且很难复现。LIMIT 和 ORDER BY 是绑定使用的。
自测三问(点开对答案)
1. `SELECT *` 有什么问题?什么情况下可以用? —— 它把不需要的列也传过来,而且表结构一改结果就多列、按位置取值的程序会错位。 临时看一眼、或者在交互式环境里探索数据时可以用;写进代码就该把列名写全。 2. `SELECT DISTINCT vend_id, prod_price` 返回 11 行,说明 DISTINCT 作用在哪? —— 作用在两个列的组合上:只要(供货商, 价格)这一对值不同就算不同的行。 它不是「vend_id 去重、价格照旧」。 3. 为什么 LIMIT 一定要配 ORDER BY? —— 因为不排序时数据库返回行的顺序不保证稳定。 分页时第 1 页和第 2 页可能给出同一行,也可能漏掉某一行。取出来的 13 行是乱序的。 下一章讲怎么让它有序:ORDER BY 单列、多列、升序降序, 以及那个最实用的组合——ORDER BY 加 LIMIT 1 找最大值。
- ☐ (本章完,下一章 第5章 排序检索数据)