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 秒实验
- 把
WHERE prod_price = 10改成WHERE prod_price = '10',结果变吗?为什么? - 用两个比较符写出 BETWEEN 5 AND 10 的等价条件,对比行数是否一致。
WHERE cust_email IS NOT NULL返回几行?和总数 5 减 2 对得上吗?
常见误区
- 用
= NULL判空。永远返回 0 行,而且不报错。必须写 IS NULL。 - 给数值加引号。
prod_price = '10'能跑,但那是隐式转换,索引失效。 数值不加引号,字符串才加。 - 以为 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 混用不加括号会算出什么。
- ☐ (本章完,下一章 第7章 数据过滤)