一个 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 秒实验
- 把
NOT prod_price BETWEEN 5 AND 10换成prod_price < 5 OR prod_price > 10,行数一样吗? - 写一句「1001 或 1003 里价格低于 5 的产品」,先用 AND/OR 写一遍,再用 IN 写一遍, 对比行数与可读性。
- 把 IN 名单里加一个 NULL(
IN (1002, NULL)),看结果变成几行,为什么?
常见误区
- AND 和 OR 混用不加括号。结果不会报错,只会悄悄多几行少几行。只要两种都有,就加括号。
- 用 NOT IN 配可能含 NULL 的子查询。一个 NULL 就让整句返回空集。 先确认那一列没有空值,或者改用 NOT EXISTS。
- 以为 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 的」这种模糊需求它做不到。 下一章讲通配符:百分号、下划线,以及它们为什么会让索引失效。
- ☐ (本章完,下一章 第8章 用通配符进行过滤)