🐬 第6章 过滤数据
📄 一句话概括

WHERE 是 SQL 的筛子:逐行判断,为真的留下。 这一章给九个操作符,再讲清三件容易栽的事。 Demo 在样例库上量出四个数: 价格等于 10 的有 2 行、BETWEEN 5 AND 10 有 5 行、供货商不是 1003 的有 7 行; cust_email IS NULL 有 2 行,而 cust_email = NULL 返回 0 行。 最后那个 0 行是整章的重点——NULL 不等于任何值,连 NULL 都不等于。

📝 本章和前面几章的关系

上一章解决「顺序」,这一章解决「范围」。 到这里,一条 SELECT 的三大件就齐了:取哪些列(第 4 章)、按什么顺序(第 5 章)、 要哪些行(这一章)。 WHERE 是全书出现频率最高的关键字,后面每一章几乎都带着它。 这一章打不牢,第 7 章的组合条件、第 13 章的 HAVING、第 14 章的子查询都会含糊。

一、WHERE 子句:从全表到符合条件的行

SELECT 列 FROM 表 WHERE 条件; 条件是一个「真/假」的判断,逐行做一遍,为真的行留下。 位置很关键:WHERE 紧跟在 FROM 后面,在 GROUP BY、ORDER BY 之前。 顺序是 WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。 这条顺序解释了一个常见疑问:WHERE 里为什么不能用 SELECT 起好的别名? 因为 WHERE 执行时 SELECT 还没跑。别名只有 ORDER BY 和 HAVING 能用。 WHERE 后面只能跟「能判真假」的东西:比较、BETWEEN、IN、LIKE、IS NULL, 或者它们的组合(第 7 章)。 不能直接跟一个列名——WHERE prod_price 在 MySQL 里能跑(非零当真),但那是隐式转换,别这么写。

二、WHERE 的九个操作符

六个比较符:=(等于)、<> 或 !=(不等于)、<、>、<=、>=。 注意判等是一个等号。 两个等号 == 在 MySQL 里不是标准写法(部分数据库支持)。 这是从 C 和 Python 过来的人第一个要改的习惯。 <> 和 != 完全等价,MySQL 两个都认。 标准写法是 <>,因为它更通用(SQL Server、Oracle、PostgreSQL 都认,!= 有的不认)。 BETWEEN a AND b:闭区间,含两端。IS NULL/IS NOT NULL:判空专用,不能用等号。 这两个是九个操作符里最容易出错的,下面单独讲。 字符串和日期的字面量必须用引号:WHERE prod_name = 'Fuses'、 WHERE order_date >= '2005-09-01'。 数值不加引号——加了能跑(隐式转换),但索引会失效,大表上就是灾难。

三、范围值检查:BETWEEN 是闭区间

BETWEEN 5 AND 10 包含 5 和 10 两个端点。 Demo 里这一句返回 5 行:2.50 那两行不算,5.99、 8.99、9.99、10.00 两行算。 这和数学里的「开区间」习惯相反,也和很多编程语言的 range 不一样。 边界值要不要含,是业务问题,SQL 的默认答案是「含」。 BETWEEN 完全可以用两个比较符代替:a >= 5 AND a <= 10。 两者结果一样,性能也一样——BETWEEN 只是好读,不是更快的写法。 BETWEEN 也适用于日期:WHERE order_date BETWEEN '2005-09-01' AND '2005-09-30'。 这里有个坑:如果列是 DATETIME(带时分秒), BETWEEN '2005-09-30' AND ... 会把 9 月 30 日 10:00 排除在外, 因为字符串 '2005-09-30' 会被当成 00:00:00。日期范围要用「小于次日零点」。

四、空值检查:NULL 不能用等号

NULL 不是「零」,也不是「空字符串」,它是「不知道」。 所以它跟任何值比较, 结果都不是真——包括跟 NULL 自己比。 Demo 里 WHERE cust_email IS NULL 返回 2 行,而 WHERE cust_email = NULL 返回 0 行。 第二句不报错,只是永远查不到东西——这是最阴的一种 bug。 判断空只能用 IS NULL 和 IS NOT NULL。 这是 SQL 的硬规定,没有「等于空」这种说法。 NULL 还会传染算术:NULL + 1 还是 NULL,SUM() 会跳过 NULL(第 12 章), COUNT(列) 也只数非空行。一个 NULL 能让一列求和的结果悄悄变小。 建表时能不加 NULL 就不加。 第 21 章讲 NOT NULL 与默认值, 把「不知道」挡在入库之前,比事后到处写 IS NULL 划算得多。

五、小结

教材小结:WHERE 筛行,操作符九个,NULL 要用 IS NULL。 本课补三条:一是判等用一个等号(<> 比 != 通用); 二是 BETWEEN 含两端,日期范围要小心时分秒; 三是 = NULL 永远返回 0 行,这是 SQL 里最反直觉的一条。

六、动手 Demo:九个操作符,逐个数出来

Demo 把九个操作符在样例库上各跑一遍,每一句都打出真实的行数。 最后两句是同一件事的两种写法——IS NULL 与 = NULL,一个给 2 行,一个给 0 行。 这一章的数都是可以自己复算的:products 13 行、customers 5 行,筛出来几行,一眼能对上。

import sqlite3
con = sqlite3.connect(":memory:")

con.execute("SELECT prod_name, prod_price FROM products WHERE prod_price = 10")
con.execute("SELECT prod_name FROM products WHERE prod_name = 'Fuses'")
con.execute("SELECT vend_id, prod_name FROM products WHERE vend_id <> 1003")
con.execute("SELECT prod_name FROM products WHERE prod_price BETWEEN 5 AND 10")
con.execute("SELECT cust_id FROM customers WHERE cust_email IS NULL")
con.execute("SELECT cust_id FROM customers WHERE cust_email = NULL")   # 0 行

实跑输出:

--------------------------------------------------------------------
第六课:过滤数据
--------------------------------------------------------------------

  【一】WHERE 子句:从「全表」到「符合条件的行」
  SQL> SELECT prod_name, prod_price FROM products WHERE prod_price = 10
  prod_name          prod_price
  ------------------------------
  Bird seed          10.00
  TNT (5 sticks)     10.00
  (2 行)
  **WHERE 写在 FROM 后面。** 它逐行判断,结果为真的行才留下。

  【二】WHERE 的操作符一览
  操作符      含义                  例子
  ------------------------------------------------------------------------------
  =           等于                  prod_price = 10
  <>          不等于                vend_id <> 1003
  !=          不等于(同上)        vend_id != 1003
  <           小于                  prod_price < 10
  <=          小于等于              prod_price <= 10
  >           大于                  prod_price > 10
  >=          大于等于              prod_price >= 10
  BETWEEN     在某个闭区间内        prod_price BETWEEN 5 AND 10
  IS NULL     是空值                cust_email IS NULL
  **注意判等是一个等号**,两个等号在某些数据库里也行,但在 MySQL 里 `==` 不是标准写法。

  【三】检查单个值:字符串要用单引号
  SQL> SELECT prod_name, prod_price FROM products WHERE prod_name = 'Fuses'
  prod_name     prod_price
  -------------------------
  Fuses         3.42
  (1 行)
  **字符串值必须用单引号**(双引号在 MySQL 里也能用,但不通用)。
  **数值不要加引号**:`prod_price = '10'` 能跑,但那是靠隐式转换,索引会失效。

  【四】不匹配检查:不等于
  SQL> SELECT vend_id, prod_name FROM products WHERE vend_id <> 1003
  vend_id     prod_name
  -------------------------
  1001        .5 ton anvil
  1001        1 ton anvil
  1001        2 ton anvil
  1002        Fuses
  1005        JetPack 2000
  1002        Oil can
  1001        Safe
  (7 行)
  SQL> SELECT vend_id, prod_name FROM products WHERE vend_id != 1003
  vend_id     prod_name
  -------------------------
  1001        .5 ton anvil
  1001        1 ton anvil
  1001        2 ton anvil
  1002        Fuses
  1005        JetPack 2000
  1002        Oil can
  1001        Safe
  (7 行)
  **两个结果一模一样。<> 是 SQL 标准写法,!= 是别名,MySQL 两个都认。**
  习惯上写 <>,因为它更通用。

  【五】范围值检查:BETWEEN 是闭区间
  SQL> SELECT prod_name, prod_price FROM products WHERE prod_price BETWEEN 5 AND 10
  prod_name          prod_price
  ------------------------------
  .5 ton anvil       5.99
  1 ton anvil        9.99
  Bird seed          10.00
  Oil can            8.99
  TNT (5 sticks)     10.00
  (5 行)
  **BETWEEN 5 AND 10 包含 5 和 10 两个端点**(5.99 和 10.00 都在里面)。
  这和数学里的开闭区间不一样,**必须记住它含两端**,否则边界值会漏。

  【六】空值检查:NULL 不能用等号
  SQL> SELECT cust_id, cust_name FROM customers WHERE cust_email IS NULL
  cust_id     cust_name
  ------------------------
  10002       Mouse House
  10005       E Fudd
  (2 行)
  SQL> SELECT cust_id FROM customers WHERE cust_email = NULL
  cust_id
  --------
  (0 行)
  **第二句返回 0 行,不是语法错——这是 NULL 的规矩:它不等于任何值,连 NULL 都不等于。**
  判断空只能用 **IS NULL** 或 **IS NOT NULL**。
  **这一条是 SQL 里最反直觉的地方,也是新手最常栽的坑。**

第一句是等值筛。 价格正好等于 10.00 的有 2 行(Bird seed 与 TNT (5 sticks))。 注意浮点数判等要小心:10.00 能精确表示所以没事,但 0.1 加 0.2 不等于 0.3, 金额一定用定点数(DECIMAL),不要用浮点。 第二句是字符串等值。 'Fuses' 用单引号,大小写敏感——写 'fuses' 查不到(MySQL 里其实能查到, 因为默认校对不区分大小写,第 27 章讲)。 第三句是不等于。 <> 1003 得到 7 行,13 减 6 正好对上——1003 有 6 个产品。 这个减法可以用来验算。 第四句是 BETWEEN。 5 行:5.99、8.99、9.99 和两个 10.00。 2.50 和 50.00 都不在里面,因为一个低于下限、一个高于上限。 第五、第六句是这一章的重点。 IS NULL 给 2 行(两位顾客没留邮箱),= NULL 给 0 行。 两句语法都合法、都不报错,但结果完全不同。 这就是为什么 NULL 必须单独对待。

30 秒实验

  1. 把 WHERE prod_price = 10 改成 WHERE prod_price = '10',结果变吗?为什么?
  2. 用两个比较符写出 BETWEEN 5 AND 10 的等价条件,对比行数是否一致。
  3. WHERE cust_email IS NOT NULL 返回几行?和总数 5 减 2 对得上吗?

常见误区

  1. 用 = NULL 判空。永远返回 0 行,而且不报错。必须写 IS NULL。
  2. 给数值加引号。prod_price = '10' 能跑,但那是隐式转换,索引失效。 数值不加引号,字符串才加。
  3. 以为 BETWEEN 是开区间。它含两端。边界值到底算不算,先想清楚业务再写。
自测三问(点开对答案) 1. WHERE 和 ORDER BY 谁先执行?这个顺序有什么实际影响? —— WHERE 先、ORDER BY 后(中间还有 GROUP BY 与 SELECT)。 所以 WHERE 里不能用 SELECT 起的别名,ORDER BY 里可以。 2. `<>` 和 `!=` 有区别吗?该用哪个? —— 在 MySQL 里完全等价。标准写法是 `<>`,通用性更好。 3. 为什么 `WHERE cust_email = NULL` 返回 0 行而不是报错? —— 因为 NULL 表示「不知道」,与任何值的比较结果都是「未知」而不是「真」, WHERE 只留结果为真的行,所以一行都留不下。判空只能用 IS NULL。

一个 WHERE 只能写一个条件。 下一章把条件组合起来:AND 与 OR 的优先级、IN 的名单写法、NOT 的取反——顺带看一个经典陷阱, AND 和 OR 混用不加括号会算出什么。

相关笔记