🐬 第4章 检索数据
📄 一句话概括

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 秒实验

  1. 把 LIMIT 5 OFFSET 10 换成 LIMIT 10 OFFSET 5,结果的行数和内容各变成什么?
  2. 试 SELECT DISTINCT vend_id, prod_id FROM products;,返回几行?为什么不是 4 行也不是 11 行?
  3. 把 SELECT prod_name FROM products LIMIT 5 连跑三次,行序每次都一样吗?如果不一致,说明什么?

常见误区

  1. 以为不排序也有固定顺序。小表上「每次都一样」纯属巧合。 要顺序就写 ORDER BY,哪怕你觉得「按主键自然是升序」。
  2. 以为 DISTINCT 只作用于第一列。DISTINCT a, b 是「a 和 b 的组合去重」。 只想去重一个列就只写一个列。
  3. 分页不写 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 找最大值。

相关笔记