🦏 第3章 查询操作
📄 一句话概括

where 是筛行的那道闸:比较运算符六个、逻辑运算符三个、 还有 between/in/like/is null 四件常用工具。 Demo 撞了两件事:phone = NULL 给 0 行(NULL 和任何值比都得「未知」); a or b and c 不加括号给 9 行、加括号给 3 行——and 比 or 高,这一条能让结果差三倍。

📝 本章和前面几章的关系

上一篇挑列,这一篇挑行。 where 是 SQL 里最容易写错也最常写错的子句。 它的位置固定在 from 之后、group by 之前, 这个顺序决定了它看不见聚合函数的结果(那是 having 的活)。

一、六个比较运算符

= 等于(注意只有一个等号,== 在 SQL 里不是运算符)、<> 不等于(也可写 !=)、 >、>=、<、<=。 字符串也能比大小:按校对规则(collation)逐字符比,所以 'abc' < 'abd' 为真。 日期也能比——前提是它存成了标准格式:'2026-04-01' < '2026-05-01' 字典序恰好等于时间序。 这也是为什么日期要存 YYYY-MM-DD。

二、逻辑运算符:and、or、not

and 要求两边都真、or 只要一边真、not 取反。 not 只作用于紧跟它的那个条件:not a = 1 and b = 2 是 (not a = 1) and (b = 2), 不是 not (a = 1 and b = 2)。 and 和 or 一混,就必须加括号。 这不只是可读性问题, 不加括号结果就是错的(下一篇的 Demo 就差三倍)。

三、四件常用工具:between、in、like、is null

between a and b 是闭区间,两头都算,等价于 >= a and <= b。 注意它不能反过来写(between 170 and 160 恒假)。 in (a, b, c) 是「等于其中任意一个」, 比一串 or 好读,而且数据量大时更快(内部会排序后二分)。 like 是模糊匹配(% 任意多个字符、_ 一个字符),第 5 章细讲。 is null 判断空值——不能写成 = null。 为什么 = null 永远不成立: NULL 的意思是「未知」,「未知等于未知」的结果还是「未知」,不是真。 所以只能用 is null / is not null。

四、优先级:and 比 or 高

从高到低:括号 → 算术 → 比较(含 is/in/like/between)→ not → and → or。 所以 a or b and c 会被理解成 a or (b and c), 而不是 (a or b) and c。 这两者的结果可能差好几倍。 Demo 的数:gender='女' or gender='男' and height>175 给 9 行,加括号变 3 行。 9 行 = 全部 6 个女生 + 3 个高于 175 的男生。

五、还有两个隐式转换的坑

类型不一致时引擎会偷偷转换:where phone = 13800001111 里 phone 是字符串, MySQL 会把它转成数字比,索引直接失效,而且可能匹配到意料之外的行。 '100' = 100 在 MySQL 里为真(转成数字), 但在严格比较的引擎里可能为假。 所以值该加引号就加引号,别指望引擎猜。 这条也是慢查询的常见原因:列是字符串、条件写数字,索引就用不上。

六、小结

教材小结: where 用比较运算符、逻辑运算符与 between/in/like/is null 筛选行;and 的优先级高于 or。 本课补三条:一是 = null 永远不成立,只能用 is null;二是 and 与 or 混用一律加括号; 三是类型要对齐,别让引擎替你隐式转换(会废掉索引)。

七、动手 Demo:六个运算符、四个工具、一个优先级坑

Demo 六段:六个比较运算符逐个给行数,再测逻辑与四件工具,然后证明 = null 恒假, 最后对比加不加括号的差异。 这一篇的数都是 12 个学生的真实分布,可以自己心算核对。

import _q46 as Q
c = Q.con()

# 六个比较运算符
for cond in ["gender = '女'", "gender <> '女'", "height > 175",
             "height >= 174", "height < 160", "height <= 160"]:
    c.execute("SELECT COUNT(*) FROM students WHERE " + cond)

# 逻辑与其它运算符
c.execute("SELECT COUNT(*) FROM students "
          "WHERE gender = '女' AND height > 160")
c.execute("SELECT COUNT(*) FROM students WHERE height BETWEEN 160 AND 170")
c.execute("SELECT COUNT(*) FROM students WHERE class_id IN (1, 3)")
c.execute("SELECT COUNT(*) FROM students WHERE name LIKE '张%'")
c.execute("SELECT COUNT(*) FROM students WHERE phone IS NULL")

# = NULL 永远不成立
c.execute("SELECT COUNT(*) FROM students WHERE phone = NULL")

# 优先级:不加括号 9 行,加括号 3 行
c.execute("SELECT COUNT(*) FROM students WHERE gender = '女' "
          "OR gender = '男' AND height > 175")
c.execute("SELECT COUNT(*) FROM students WHERE (gender = '女' "
          "OR gender = '男') AND height > 175")

实跑输出:

--------------------------------------------------------------------
第十一课:where 的运算符,以及优先级那个坑
--------------------------------------------------------------------

  比较运算符:六个,等于只有一个等号
  =    gender = '女'             ->  6 行   等于,注意是一个等号
  <>   gender <> '女'            ->  6 行   不等于,也可写 !=
  >    height > 175             ->  3 行   大于
  >=   height >= 174            ->  4 行   大于等于
  <    height < 160             ->  1 行   小于
  <=   height <= 160            ->  1 行   小于等于

  **`=` 是判断相等,`==` 在 SQL 里不是运算符**——写惯了别的语言最容易错这一处。

  逻辑运算符:and、or、not
  gender = '女' AND height > 160    ->  5 行
  gender = '女' OR height > 175     ->  9 行
  NOT gender = '女'                 ->  6 行

  **`not` 只作用于紧跟它的那个条件**,所以 `not a = 1 and b = 2` 是 `(not a = 1) and (b = 2)`。
  **加括号最稳。**

  其他运算符:between、in、like、is null
  height BETWEEN 160 AND 170       ->  6 行
  class_id IN (1, 3)               ->  8 行
  name LIKE '张%'                   ->  1 行
  phone IS NULL                    ->  1 行

  **`between 160 and 170` 是闭区间,两头都算。** **`is null` 不能写成 `= null`**——
  NULL 和任何值比较都得「未知」,永远筛不出来。

  SQL> SELECT COUNT(*) AS with_eq_null FROM students WHERE phone = NULL
  with_eq_null
  -------------
  0
  (1 行)
  **这一句给 0 行,就是「= NULL 永远不成立」的证据。**

  优先级:and 比 or 高,这一条能坑死人
  不加括号:9 行
  加括号:  3 行

  **不加括号时,引擎先算 `gender = '男' AND height > 175`,再和前半段 or 起来——**
  **所以它等于「所有女生」加「身高超过 175 的男生」,而不是你要的「所有人都高于 175」。**
  **规矩只有一条:where 里同时出现 and 和 or,一律加括号。**

  优先级从高到低,记这四层就够
  1. 括号
  2. 算术运算(+ - * /)
  3. 比较运算(= <> > < >= <=)与 IS、IN、LIKE、BETWEEN
  4. NOT → AND → OR

第一段六个比较。 女生 6 个、男生 6 个; > 175 给 3 行、>= 174 给 4 行——差的那一行正好是 174.0 的周涛。 第二段逻辑。 女 and >160 给 5 行、女 or >175 给 9 行、not 女 给 6 行。 第三段四件工具。 between 6 行、in 8 行、like 1 行、 is null 1 行——孙悦没填手机号,所以 is null 命中她一个。 第四段 = null。 给 0 行,这就是「NULL 比较恒为未知」的证据。 第五段优先级。 不加括号 9 行、加括号 3 行,同一句话,两种意思。 第六段优先级表。 四层,够用了。

30 秒实验

  1. height between 160 and 170 与 height >= 160 and height <= 170 结果一样吗?
  2. (先想再跑)where phone is not null 给几行?
  3. 把 not gender = '女' 改成 not (gender = '女'),结果变吗?

常见误区

  1. 写 where 列 = null。 永远 0 行,而且不报错,最难查。
  2. and 与 or 混用不加括号。 结果悄悄错,不报错,靠对数才发现。
  3. 字符串列拿数字比。 隐式转换让索引用不上,还会匹配到意料之外的行。
自测三问(点开对答案) 1. 为什么 `= null` 永远筛不出行? —— NULL 表示「未知」,与任何值比较的结果都是「未知」,不是真; 判断空值只能 is null/is not null。 2. and 与 or 哪个优先级高?不加括号会怎样? —— and 更高。`a or b and c` 会被算成 `a or (b and c)`,和 `(a or b) and c` 结果可能差很多。 3. between 是闭区间还是开区间? —— 闭区间,两头都算,等价于 `>= 小 and <= 大`;反过来写恒为假。

行筛出来了,但出来的顺序是随机的。 下一篇 order by:怎么排,以及中文到底按什么排。

相关笔记