Menu

SQLite WHERE 子句:用条件、LIKE、IN、BETWEEN 筛选行

WHERE 子句在 SQLite 中如何筛选行:比较运算符、AND/OR、LIKE、IN、BETWEEN,以及几乎人人都会踩的 NULL 陷阱。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

WHERE 逐行筛选

不带 WHERE 的 SELECT 会返回表里的每一行,而这很少是你想要的。WHERE 让你只保留符合条件的行:SQLite 遍历整张表,对每一行计算条件,保留结果为真的那些行。

返回三行:Neuromancer、Hyperion 和 The Martian。条件 year > 1980 对每一行都检测了一遍,只有匹配的行留了下来。

可以这样理解:WHERE 是夹在 FROM 和你选择的列之间的一道过滤器,凡是结果为真的行都能通过。

比较运算符

基础运算符的行为和你预想的一样:

= 表示相等,!= 或 <> 表示“不等于”,<、<=、>、>= 用于比较大小。字符串比较用的也是这些运算符,author = 'Asimov' 会逐字符精确匹配。

有一点要注意:SQL 用单引号表示字符串字面量,双引号用于标识符(列名或表名)。由于历史原因,WHERE author = "Asimov" 在 SQLite 里也许能用,但它不可移植,而且当这个“字符串”恰好和某个列名相同时,会悄无声息地出错。坚持用单引号。

AND、OR 和括号

实际的查询通常要组合多个条件。AND 要求两边都为真;OR 只要求至少一边为真:

第一个查询筛选出既较新又较薄的书。第二个查询取出两位作者中任意一位写的书。

混用 AND 和 OR 时,优先级很容易坑人。AND 的优先级比 OR 高,所以:

会被理解为 Herbert OR (Gibson AND year > 1980):Herbert 的所有书(不论年份),再加上 Gibson 在 1980 年之后的书。这多半不是你的本意。用括号把意图写明白:

拿不准的时候就加括号。查询优化器不在乎,下一个读这段代码的人会感谢你。

NULL 不像普通的值

这是 WHERE 子句里每个人都会掉进去一次的坑。SQL 中的 NULL 表示“未知”,而未知是无法比较的。column = NULL 的结果不是假,而是 NULL,WHERE 会把它当作“跳过这一行”。

IS NULL 和 IS NOT NULL 是仅有的能直接检测 NULL 的运算符。把它们刻进肌肉记忆:其他任何和 NULL 的比较都会返回 NULL,并悄悄丢掉这些行。

否定条件也适用同样的规则。WHERE author != 'Asimov' 不会返回 author IS NULL 的行,因为 NULL != 'Asimov' 的结果同样是 NULL。如果你想把 NULL 也包含进来,就要明确写出来:WHERE author != 'Asimov' OR author IS NULL。

IN 和 BETWEEN:天天都会用的简写

IN 检查某个值是否在列表中,比写一串 OR 更简洁:

BETWEEN 检查一个范围,两端都包含在内:

year BETWEEN 1980 AND 2000 与 year >= 1980 AND year <= 2000 完全等价,只是更短。要记住一点:上下界都包含在内。如果你想排除边界,就把比较条件分开写。

关于 IN 和 NULL 再补充一句:WHERE column NOT IN (1, 2, NULL) 永远不会返回任何行,因为任何值和 NULL 比较都得到 NULL。要么把 NULL 从列表里去掉,要么用 IS NULL 单独处理。

用 LIKE 做模式匹配

LIKE 用两种通配符做字符串模式匹配:

  • % 匹配任意长度的字符序列(包括空)。
  • _ 恰好匹配一个字符。

默认情况下,SQLite 的 LIKE 对 ASCII 字母不区分大小写,'Dune' LIKE 'dune' 为真。如果你之前用的是 Postgres,这会让你意外:在 Postgres 中 LIKE 区分大小写,不区分大小写的版本是 ILIKE。(SQLite 没有 ILIKE。)

如果你确实需要区分大小写的匹配,有两种办法。一是切换全局 pragma:

PRAGMA case_sensitive_like = ON;

二是使用 GLOB,它始终区分大小写,并使用 Unix 风格的通配符(* 匹配任意序列,? 匹配一个字符):

在这里 GLOB 'd*' 什么也匹配不到,因为大小写很重要。

筛选日期

SQLite 把日期存成文本(通常是 YYYY-MM-DD 或完整的 ISO 8601 格式),这意味着只要坚持使用 ISO 格式,字符串比较恰好也能当作日期比较:

因为 '2024-06-01' < '2024-11-08' 无论按字符串还是按日期比较都为真,所以这些查询能得到预期的结果。如果你用其他格式存日期('15/01/2024'、'Jan 15 2024'),比较结果就会悄悄出错。始终使用 ISO 8601,将来的你会感谢现在的自己。

对于更复杂的日期计算(提取年份、和“今天”比较),SQLite 提供了 date()、strftime() 和 julianday() 函数。我们会在日期和时间那一章讲到。

综合运用

一个同时用到上面多种写法的查询:

逐行来读:保留年份已知、在范围内、出自两位作者之一或者篇幅够长、并且不是草稿的行。这正是 WHERE 子句最拿手的事:把一个个小而易读的条件组合成精确的筛选。

两个值得保持的习惯:

  • 每个条件单独缩进成一行。很长的 WHERE 子句挤成一行,很快就没法读了。
  • 条件不够直观时,用注释写明意图。-- exclude drafts 这样的注释成本很低,却很有保障。

下一篇:深入了解运算符与 NULL

WHERE 子句主要就是把运算符作用在列上,而 NULL 会悄悄改变每个运算符的行为。下一页会深入讲解 SQLite 的运算符:算术运算、用 || 拼接字符串、IS 系列运算符以及三值逻辑,让这些意外不再意外。

常见问题

SQLite 中的 WHERE 子句是怎么工作的?

WHERE 会对每一行检测一个条件,从而筛选查询结果。条件为真的行被保留;条件为假或为 NULL 的行被丢弃。它紧跟在 FROM 后面:SELECT ... FROM table WHERE condition。

在 SQLite 的 WHERE 子句中如何组合多个条件?

用 AND 和 OR。AND 要求两边都为真;OR 只要求其中一边为真。AND 的优先级比 OR 高,所以混用时要加括号把意图写清楚:WHERE (a OR b) AND c。

为什么在 SQLite 里 WHERE column = NULL 不起作用?

NULL 表示“未知”,所以任何用 = 或 != 和它做的比较都会返回 NULL,而不是真或假,而只有条件为真时行才会被保留。请改用 IS NULL 和 IS NOT NULL,它们是仅有的能直接检测 NULL 的运算符。

SQLite WHERE 子句里的 LIKE 区分大小写吗?

默认情况下,LIKE 对 ASCII 字符不区分大小写,'Hello' LIKE 'hello' 为真。如果要完全区分大小写,可以设置 PRAGMA case_sensitive_like = ON;,或者改用 GLOB:它始终区分大小写,并使用 Unix 风格的通配符(* 和 ?)。

Coddy 编程语言插图

和 Coddy 一起学编程

开始