SELECT * FROM t_student
WHERE age = 20 OR age <> 20;
咋一看,这不就是查询表中全部记录吗?我们来看下实际结果
yzb 没查出来,这是为什么了?我们来分析下,yzb 的 age 是 NULL,那么这条记录的判断步骤如下
-- 1. 约翰年龄是 NULL (未知的 NULL !)
SELECT *
FROM t_student
WHERE age = NULL
OR age <> NULL;
-- 2. 对 NULL 使用比较谓词后,结果为unknown
SELECT *
FROM t_student
WHERE unknown
OR unknown;
-- 3.unknown OR unknown 的结果是unknown (参考三值逻辑的逻辑值表)
SELECT *
FROM t_student
WHERE unknown;
-- 添加 3 个条件:年龄是20 岁,或者不是20 岁,或者年龄未知
SELECT * FROM t_student
WHERE age = 20
OR age <> 20
OR age IS NULL;
CASE 表达式和 NULL
简单 CASE 表达式如下
CASE col_1
WHEN = 1 THEN 'o'
WHEN NULL THEN 'x'
END
这个 CASE 表达式一定不会返回 ×。这是因为,第二个 WHEN 子句是 col_1 = NULL 的缩写形式。正如我们所知,这个式子的逻辑值永远是 unknown ,而且 CASE 表达式的判断方法与 WHERE 子句一样,只认可逻辑值为 true 的条件。正确的写法是像下面这样使用搜索 CASE 表达式
CASE WHEN col_1 = 1 THEN 'o'
WHEN col_1 IS NULL THEN 'x'
END
NOT IN 和 NOT EXISTS 不是等价的
我们在对 SQL 语句进行性能优化时,经常用到的一个技巧是将 IN 改写成 EXISTS ,这是等价改写,并没有什么问题。但是,将 NOT IN 改写成 NOT EXISTS 时,结果未必一样。
我们来看个例子,我们有如下两张表:t_student_A 和 t_student_B,分别表示 A 班学生与 B 班学生
DROP TABLE IF EXISTS t_student_A;
CREATE TABLE t_student_A (
id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
name VARCHAR(50) NOT NULL COMMENT '名称',
age INT(3) COMMENT '年龄',
city VARCHAR(50) NOT NULL COMMENT '城市',
remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
primary key(id)
) COMMENT '学生信息';
INSERT INTO t_student_A(name, age, city)
VALUE
('zhangsan', 25,'深圳市'),('wangwu', 60, '广州市'),
('bruce', 32, '北京市'),('yzb', NULL, '深圳市'),
('boss', 43, '深圳市');
DROP TABLE IF EXISTS t_student_B;
CREATE TABLE t_student_B (
id INT(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
name VARCHAR(50) NOT NULL COMMENT '名称',
age INT(3) COMMENT '年龄',
city VARCHAR(50) NOT NULL COMMENT '城市',
remark VARCHAR(500) NOT NULL DEFAULT '' COMMENT '备注',
primary key(id)
) COMMENT '学生信息';
INSERT INTO t_student_B(name, age, city)
VALUE
('马化腾', 45, '深圳市'),('马三', 25, '深圳市'),
('马云', 43, '杭州市'),('李彦宏', 41, '深圳市'),
('年轻人', 25, '深圳市');
* FROM t_student_B;
需求:查询与 A 班住在深圳的学生年龄不同的 B 班学生,也就说查询出 :马化腾 和 李彦宏,这个 SQL 该如何写,像这样?
-- 查询与 A 班住在深圳的学生年龄不同的 B 班学生 ?
SELECT * FROM t_student_B
WHERE age NOT IN (
SELECT age FROM t_student_A
WHERE city = '深圳市'
);
-- 1. 执行子查询,获取年龄列表
SELECT * FROM t_student
WHERE age NOT IN(43, NULL, 25);
-- 2. 用NOT 和IN 等价改写NOT IN
SELECT * FROM t_student
WHERE NOT age IN (43, NULL, 25);
-- 3. 用OR 等价改写谓词IN
SELECT * FROM t_student
WHERE NOT ( (age = 43) OR (age = NULL) OR (age = 25) );
-- 4. 使用德· 摩根定律等价改写
SELECT * FROM t_student
WHERE NOT (age = 43) AND NOT(age = NULL) AND NOT (age = 25);
-- 5. 用<> 等价改写 NOT 和 =
SELECT * FROM t_student
WHERE (age <> 43) AND (age <> NULL) AND (age <> 25);
-- 6. 对NULL 使用<> 后,结果为 unknown
SELECT * FROM t_student
WHERE (age <> 43) AND unknown AND (age <> 25);
-- 7.如果 AND 运算里包含 unknown,则结果不为true(参考三值逻辑的逻辑值表)
SELECT * FROM t_student
WHERE false 或 unknown;
可以看出,在进行了一系列的转换后,没有一条记录在 WHERE 子句里被判断为 true 。也就是说,如果 NOT IN 子查询中用到的表里被选择的列中存在 NULL ,则 SQL 语句整体的查询结果永远是空。这是很可怕的现象!
为了得到正确的结果,我们需要使用 EXISTS 谓词
-- 正确的SQL 语句:马化腾和李彦宏将被查询到
SELECT * FROM t_student_B B
WHERE NOT EXISTS (
SELECT * FROM t_student_A A
WHERE B.age = A.age
AND A.city = '深圳市'
);
执行结果如下
同样地,我们再来一步一步地看看这段 SQL 是如何处理年龄为 NULL 的行的
-- 1. 在子查询里和 NULL 进行比较运算,此时 A.age 是 NULL
SELECT * FROM t_student_B B
WHERE NOT EXISTS (
SELECT * FROM t_student_A A
WHERE B.age = NULL
AND A.city = '深圳市'
);
-- 2. 对NULL 使用“=”后,结果为 unknown
SELECT * FROM t_student_B B
WHERE NOT EXISTS (
SELECT * FROM t_student_A A
WHERE unknown
AND A.city = '深圳市'
);
-- 3. 如果AND 运算里包含 unknown,结果不会是true
SELECT * FROM t_student_B B
WHERE NOT EXISTS (
SELECT * FROM t_student_A A
WHERE false 或 unknown
);
-- 4. 子查询没有返回结果,因此相反地,NOT EXISTS 为 true
SELECT * FROM t_student_B B
WHERE true;
也就是说,yzb 被作为 “与任何人的年龄都不同的人” 来处理了。EXISTS 只会返回 true 或者false,永远不会返回 unknown。因此就有了 IN 和 EXISTS 可以互相替换使用,而 NOT IN和 NOT EXISTS 却不可以互相替换的混乱现象。
还有一些其他的陷阱,比如:限定谓词和 NULL、限定谓词和极值函数不是等价的、聚合函数和 NULL 等等。 总结
1、NULL 用于表示缺失的值或遗漏的未知数据,不是某种具体类型的值,不能对其使用谓
2、对 NULL 使用谓词后的结果是 unknown,unknown 参与到逻辑运算时,SQL 的运行会和预想的不一样3、 IS NULL 整个是一个谓词,而不是:IS 是谓词,NULL 是值;类似的还有 IS TRUE、IS FALSE4、要想解决 NULL 带来的各种问题,最佳方法应该是往表里添加 NOT NULL 约束来尽力排除 NULL
以上就是详解mysql三值逻辑与NULL的详细内容,更多关于mysql三值逻辑与NULL的资料请关注脚本之家其它相关文章!