论关系型数据库中 NULL 值对 SQL 查询语句的影响
假设有如下一张表,名为 Product
(DDL 见本文结尾):
product_id | product_name | product_type | sale_price | purchase_price | regist_date |
---|---|---|---|---|---|
0001 | T恤衫 | 衣服 | 1000 | 500 | 2009-09-20 |
0002 | 打孔器 | 办公用品 | 500 | 320 | 2009-09-11 |
0003 | 运动T恤 | 衣服 | 4000 | 2800 | NULL |
0004 | 菜刀 | 厨房用具 | 3000 | 2800 | 2009-09-20 |
0005 | 高压锅 | 厨房用具 | 6800 | 5000 | 2009-01-15 |
0006 | 叉子 | 厨房用具 | 500 | NULL |
2009-09-20 |
0007 | 擦菜板 | 厨房用具 | 880 | 790 | 2008-04-28 |
0008 | 圆珠笔 | 办公用品 | 100 | NULL |
2009-11-11 |
DISTINCT
在使用 DISTINCT
时,NULL
也被视为一类数据。NULL
存在于多行中时,也会被合并为一条 NULL
数据。
对含有 NULL
数据的列使用 DISTINCT
关键字:
SELECT DISTINCT purchase_price
FROM Product;
执行结果为:
purchase_price |
---|
500 |
320 |
2800 |
5000 |
NULL |
790 |
算术函数
考虑一下在 SQL 语句中如下运算的结果:
5 + NULL
10 - NULL
1 * NULL
4 / NULL
NULL / 9
NULL / 0
正确答案是全为 NULL
。实际上所有包含 NULL
的计算,结果肯定是 NULL
。
比较谓词
当我们尝试取出不是进货单价不是 2800
日元(purchase_price <> 2800
)的记录时:
SELECT product_name, purchase_price
FROM Product
WHERE purchase_price <> 2800;
执行结果为:
product_name | purchase_price |
---|---|
T恤衫 | 500 |
打孔器 | 320 |
高压锅 | 5000 |
擦菜板 | 790 |
执行结果中并没有“叉子”和“圆珠笔”。这两条记录由于进货单价不明(NULL
),因此无法判定是不是 2800
日元。
当你尝试使用 purchase_price = NULL
或 purchase_price <> NULL
作为查询条件时,均查询不到任何记录。
实际上,在 SQL 中,比较运算符的结果除了真(TRUE)与假(FALSE)之外,还有第三种值——不确定(UNKNOWN)。
NULL 与所有值比较,结果都为不确定。
替代的,你可以使用 purchase_price IS NULL
和 purchase_price IS NOT NULL
来查询你想要的记录。如:
SELECT product_name, purchase_price
FROM Product
WHERE purchase_price IS NULL;
执行结果为:
product_name | purchase_price |
---|---|
叉子 | NULL |
圆珠笔 | NULL |
逻辑谓词
下列是三值逻辑中 AND
和 OR
运算的真值表:
P | Q | P AND Q | P OR Q |
---|---|---|---|
真 | 真 | 真 | 真 |
真 | 假 | 假 | 真 |
真 | 不确定 | 不确定 | 真 |
假 | 假 | 假 | 假 |
假 | 不确定 | 假 | 不确定 |
不确定 | 不确定 | 不确定 | 不确定 |
使查询语句
WHERE
子句的值为不确定的记录,不会被查询出来。
聚合
聚合函数
除 COUNT(*)
外,聚合函数只对 NULL
以外的对象进行汇总。
执行如下查询语句,结果已在注释中标出:
SELECT COUNT(*)
FROM Product;
-- 结果为 8
SELECT COUNT(purchase_price)
FROM Product;
-- 结果为 6
SELECT SUM(purchase_price)
FROM Product;
-- 结果为 12210(500 + 320 + 2800 + 2800 + 5000 + 790)
SELECT AVG(purchase_price)
FROM Product;
-- 结果为 2035((500 + 320 + 2800 + 2800 + 5000 + 790) / 6)
-- 注意:分母为 6 而不是 8,聚合函数只对 NULL 以外的对象进行汇总
GROUP BY
当聚合键中包含 NULL
时,也会将 NULL
作为一组特定的数据:
SELECT purchase_price, COUNT(*)
FROM Product
GROUP BY purchase_price;
执行结果为:
purchase_price | COUNT(*) |
---|---|
NULL |
2 |
320 | 1 |
500 | 1 |
790 | 1 |
2800 | 2 |
5000 | 1 |
排序
使用含有 NULL 的列作为排序键时,NULL 会在结果的开头或末尾汇总显示:
SELECT product_id, product_name, sale_price, purchase_price
FROM Product
ORDER BY purchase_price;
执行结果为:
product_id | product_name | sale_price | purchase_price |
---|---|---|---|
0006 | 叉子 | 500 | NULL |
0008 | 圆珠笔 | 100 | NULL |
0002 | 打孔器 | 500 | 320 |
0001 | T恤衫 | 1000 | 500 |
0007 | 擦菜板 | 880 | 790 |
0003 | 运动T恤 | 4000 | 2800 |
0004 | 菜刀 | 3000 | 2800 |
0005 | 高压锅 | 6800 | 5000 |
究竟是在开头显示还是在末尾显示,并没有特殊规定。某些 DBMS 中可以指定 NULL 在开头或末尾显示。
附录
Product 表 DDL
-- MySQL 5.7
CREATE TABLE Product
(product_id CHAR(4) NOT NULL,
product_name VARCHAR(100) NOT NULL,
product_type VARCHAR(32) NOT NULL,
sale_price INTEGER ,
purchase_price INTEGER ,
regist_date DATE ,
PRIMARY KEY (product_id));
START TRANSACTION;
INSERT INTO Product VALUES ('0001', 'T恤' ,'衣服', 1000, 500, '2009-09-20');
INSERT INTO Product VALUES ('0002', '打孔器', '办公用品', 500, 320, '2009-09-11');
INSERT INTO Product VALUES ('0003', '运动T恤', '衣服', 4000, 2800, NULL);
INSERT INTO Product VALUES ('0004', '菜刀', '厨房用具', 3000, 2800, '2009-09-20');
INSERT INTO Product VALUES ('0005', '高压锅', '厨房用具', 6800, 5000, '2009-01-15');
INSERT INTO Product VALUES ('0006', '叉子', '厨房用具', 500, NULL, '2009-09-20');
INSERT INTO Product VALUES ('0007', '擦菜板', '厨房用具', 880, 790, '2008-04-28');
INSERT INTO Product VALUES ('0008', '圆珠笔', '办公用品', 100, NULL, '2009-11-11');
COMMIT;