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

一个 WHERE 只能写一个条件,这一章把条件连起来: AND(都要真)、OR(一个真就够)、IN(名单)、NOT(取反)。 Demo 量出一个吓人的数:同一句混用 AND 与 OR 的条件, 不加括号返回 6 行,加括号返回 3 行——差的那 3 行就是被 AND 先算 悄悄吃掉的。 所以这一章的规矩只有一条:混用必加括号。

📝 本章和前面几章的关系

上一章学会筛一行,这一章学会筛一片。 WHERE 的真正威力全在组合上——真实业务的查询条件几乎没有单条的。 这一章还埋了两颗雷:AND 与 OR 的优先级,以及 NOT IN 碰上 NULL。 两颗都不报错、都算得出结果,只是结果是错的。这类 bug 最难查。

一、AND:两个条件都得成立

WHERE 条件1 AND 条件2 的意思是「两个都为真才留下」。它收窄结果集——条件越多,行越少。 Demo 里「供货商是 1003」有 6 个产品,再加「价格不高于 10」,只剩 5 个。 被筛掉的是 Detonator(13.00)。 AND 可以连很多个:A AND B AND C。每一个都必须是真,全真才留。

二、OR:一个成立就够

WHERE 条件1 OR 条件2 是「有一个真就留下」。 它放大结果集——条件越多,行越多(可能重复,但 SQL 不返回重复行)。 Demo 里「1002 或 1005」,前者 2 个产品、后者 1 个,合起来 3 行。 AND 和 OR 是同级的两个逻辑连接词,就像「与」和「或」。 但它们在数据库里的优先级不一样,这就是下一个坑。

三、计算次序:AND 比 OR 先算

SQL 规定 AND 的优先级高于 OR,跟算术里乘除高于加减一个道理。 所以 A OR B AND C 会被理解成 A OR (B AND C),不是 (A OR B) AND C。 Demo 把这个差别量出来了:查「供货商 1003,或者供货商 1002 里价格不低于 10 的」, 不加括号得 6 行(1003 全部 6 个,1002 一个都不满足), 加括号得 3 行(1003 或 1002 里价格不低于 10 的,1003 里正好 3 个)。 两个结果差 3 行,差的正是「1003 里那三个便宜货」。 如果你想要的是后者,写成了前者, 不会报错,只是数据悄悄多了三行。 所以规矩是:AND 和 OR 混用,永远加括号。 哪怕你确定优先级对, 加括号也是给下一个读代码的人看的——没人愿意在脑子里跑一遍优先级表。

四、IN:名单式写法

WHERE vend_id IN (1002, 1005) 等于 vend_id = 1002 OR vend_id = 1005,但更短、更好读、 更好改。 IN 有两个 OR 给不了的好处。 一是名单可以套子查询(第 14 章):IN (SELECT ...), OR 做不到;二是名单长的时候不会难看,十几个值用 OR 连起来基本没法读。 NOT IN 是不在名单里。 但这里有个必须知道的坑: 名单里只要有 NULL,整句就返回空集——因为 x <> NULL 永远是「未知」,不是真。 子查询给 NOT IN 供数时,先确认那一列没有 NULL。 IN 的执行效率和 OR 差不多,别指望它更快。它省的是人的力气,不是机器的。

五、NOT:真正的价值是「套住别人」

WHERE NOT vend_id = 1003 等于 vend_id <> 1003,两种写法都行。 但 NOT 的真正价值不在比较符上——比较符自己就有反向写法(= 对 <>、< 对 >=)。 NOT 值钱的地方是它能套住那些没有反向符号的条件:NOT IN、NOT BETWEEN、NOT LIKE、 NOT REGEXP。 Demo 里 NOT prod_price BETWEEN 5 AND 10 返回 8 行:13 行减去区间内的 5 行,正好对上。 这种减法验算很值得养成习惯。 NOT 的位置有讲究:WHERE NOT a = 1 和 WHERE a <> 1 一样, 但 WHERE NOT (a = 1 AND b = 2) 是对整个括号取反,这是 NOT 最有用的时候。

六、小结

教材小结:AND 收窄、OR 放大、IN 是名单、NOT 取反,混用加括号。 本课补三条:一是 AND 优先级高于 OR,不加括号会悄悄多出几行; 二是 NOT IN 碰上 NULL 会返回空集;三是 IN 能套子查询,OR 不能。

七、动手 Demo:把两种括号的差别量出来

Demo 的五段对着教材 7.1~7.3 走:AND、OR、计算次序、IN 与 NOT IN、NOT。 第三段是重点——同一句条件,一个写法 6 行、另一个写法 3 行。 每个行数都能自己验算:1003 有 6 个产品,1002 有 2 个,1005 有 1 个。

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

# AND:两个都要真
con.execute("SELECT prod_id FROM products WHERE vend_id = 1003 AND prod_price <= 10")
# OR:一个真就留
con.execute("SELECT prod_id FROM products WHERE vend_id = 1002 OR vend_id = 1005")
# 次序:AND 先算,混用必须加括号
con.execute("SELECT COUNT(*) FROM products WHERE vend_id = 1003 "
            "OR vend_id = 1002 AND prod_price >= 10")
con.execute("SELECT COUNT(*) FROM products WHERE (vend_id = 1003 "
            "OR vend_id = 1002) AND prod_price >= 10")
# IN 名单、NOT 取反
con.execute("SELECT prod_id FROM products WHERE vend_id IN (1002, 1005)")
con.execute("SELECT prod_name FROM products WHERE NOT prod_price BETWEEN 5 AND 10")

实跑输出:

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

  【一】AND:两个条件都要真
  SQL> SELECT prod_id, prod_name,
       prod_price FROM products WHERE vend_id = 1003 AND prod_price <= 10
  prod_id     prod_name          prod_price
  ------------------------------------------
  FB          Bird seed          10.00
  FC          Carrots            2.50
  SLING       Sling              4.49
  TNT1        TNT (1 stick)      2.50
  TNT2        TNT (5 sticks)     10.00
  (5 行)
  1003 有 6 个产品,价格不高于 10 的只剩这几个——**AND 让两个条件同时成立才留。**

  【二】OR:一个条件真就留
  SQL> SELECT prod_id, vend_id, prod_price FROM products WHERE vend_id = 1002 OR vend_id = 1005
  prod_id     vend_id     prod_price
  -----------------------------------
  FU1         1002        3.42
  JP2000      1005        55.00
  OL1T        1002        8.99
  (3 行)
  **OR 会放大结果集**(1002 两个 + 1005 一个),AND 会收窄。
  这一对是「与/或」,别和逻辑电路里的名字搞混。

  【三】计算次序:AND 比 OR 先算,这是个坑
  不加括号:`vend_id = 1003 OR vend_id = 1002 AND prod_price >= 10`
  返回 6 行。**数据库先算了 AND**,等于「1003 的全部,或者 1002 里贵的」。
  加括号:`(vend_id = 1003 OR vend_id = 1002) AND prod_price >= 10`
  返回 3 行。**这才是「1003 或 1002 里贵的那些」。**
  **两个结果差了 3 行,差的就是那批「1003 里的便宜货」。**
  **规矩:AND 和 OR 混用,永远加括号。**

  【四】IN:名单式写法
  SQL> SELECT prod_id, vend_id, prod_price FROM products WHERE vend_id IN (1002, 1005)
  prod_id     vend_id     prod_price
  -----------------------------------
  FU1         1002        3.42
  JP2000      1005        55.00
  OL1T        1002        8.99
  (3 行)
  和 `vend_id = 1002 OR vend_id = 1005` 结果一样,但**IN 更好读、更好改**。
  **IN 还有两个实在的好处**:一是名单可以很长(OR 写十来个就难看了),
  二是**IN 可以套子查询**(第 14 章),OR 不行。
  SQL> SELECT prod_id FROM products WHERE vend_id NOT IN (1001, 1003)
  prod_id
  --------
  FU1
  JP2000
  OL1T
  (3 行)
  **NOT IN 是「不在名单里」**,等于把 IN 的结果反过来。
  **注意:NOT IN 遇到 NULL 会出问题**——名单里有 NULL 时,整句可能返回空,
  因为 `x <> NULL` 永远是未知。这一点第 6 章讲过 NULL 的脾气。

  【五】NOT:取反,而且能套在别的东西上
  SQL> SELECT prod_name FROM products WHERE NOT vend_id = 1003
  prod_name
  -------------
  .5 ton anvil
  1 ton anvil
  2 ton anvil
  Fuses
  JetPack 2000
  Oil can
  Safe
  (7 行)
  这句和 `vend_id <> 1003` 等价。
  **NOT 的真正价值不在比较符上**,而在它能套住 IN、BETWEEN、LIKE、
  REGEXP——那些没有「反向符号」的条件。
  SQL> SELECT prod_name, prod_price FROM products WHERE NOT prod_price BETWEEN 5 AND 10
  prod_name         prod_price
  -----------------------------
  2 ton anvil       14.99
  Detonator         13.00
  Carrots           2.50
  Fuses             3.42
  JetPack 2000      55.00
  Safe              50.00
  Sling             4.49
  TNT (1 stick)     2.50
  (8 行)

第一段 AND。 1003 的 6 个产品里,价格不高于 10 的留下 5 个,Detonator(13.00)被筛掉。 这就是「收窄」。 第二段 OR。 1002 的 2 个加 1005 的 1 个,共 3 行。这就是「放大」。 第三段是这一章的核心。 注意两句只差了括号,结果差 3 行。 不加括号时数据库先算 AND,那句实际意思是「1003 的全部,或者 1002 里贵的」——1002 里没有贵的, 所以结果就是 1003 的全部 6 行。加了括号才是「1003 或 1002 里贵的」,剩 3 行。 第四段 IN 与 NOT IN。 IN 结果和 OR 那句一模一样(3 行);NOT IN 是反过来——13 减 3 等于 10 行。 用减法验算一遍,比盯着结果看可靠。 第五段 NOT。 NOT vend_id = 1003 给 7 行(13 减 6), NOT prod_price BETWEEN 5 AND 10 给 8 行(13 减 5)。 两个减法都对得上,说明 NOT 就是「取补集」。

30 秒实验

  1. 把 NOT prod_price BETWEEN 5 AND 10 换成 prod_price < 5 OR prod_price > 10,行数一样吗?
  2. 写一句「1001 或 1003 里价格低于 5 的产品」,先用 AND/OR 写一遍,再用 IN 写一遍, 对比行数与可读性。
  3. 把 IN 名单里加一个 NULL(IN (1002, NULL)),看结果变成几行,为什么?

常见误区

  1. AND 和 OR 混用不加括号。结果不会报错,只会悄悄多几行少几行。只要两种都有,就加括号。
  2. 用 NOT IN 配可能含 NULL 的子查询。一个 NULL 就让整句返回空集。 先确认那一列没有空值,或者改用 NOT EXISTS。
  3. 以为 IN 比 OR 快。两者执行计划基本一样。IN 省的是人的力气,不是机器的。
自测三问(点开对答案) 1. `A OR B AND C` 会被理解成什么? —— `A OR (B AND C)`——AND 的优先级高于 OR。要让 A、B 先组合,必须写 `(A OR B) AND C`。 2. IN 比 OR 好在哪两个地方? —— 名单长时可读性好;可以套子查询(`IN (SELECT ...)`),OR 做不到。 3. 为什么 `NOT IN (1, 2, NULL)` 可能返回空集? —— 因为判「不等于 NULL」的结果是「未知」而不是真,整行就留不下来。 名单里混进 NULL,整句的判定就塌了。

WHERE 能精确匹配,但「名字里带 anvil 的」这种模糊需求它做不到。 下一章讲通配符:百分号、下划线,以及它们为什么会让索引失效。

相关笔记