普通语言里的布尔型只有true和false两个值,这种逻辑体系被称为二值逻辑。而在SQL语言里,除此之外还有第三个值unknown,因此这种逻辑体系被称为三值逻辑(three-valued logic)。关系数据库里引进了NULL,所以不得不同时引进第三个值。
两种NULL、三值逻辑还是四值逻辑
两种NULL分别指的是“未知”(unkown)和“不适用”(not applicable, inapplicable)。科德曾经认为应该严格地分为两种类型NULL,并提倡在关系数据库中使用四值逻辑。现在所有的DBMS都将两种类型的NULL归为一类并采用了三值逻辑。
为什么必须写成“IS NULL”,而不是“=NULL”
对NULL使用比较谓词得到的结果总是unkown。查询结果只会包含WHERE子句里的判断结果为true的行,不会包含判断结果为false和unkown的行。不只是等号,对NULL使用其他比较谓词,结果也都是一样的。这是因为,NULL既不是值也不是变量。NUll只是一个表示“没有值”的标记,而比较谓词只适用于值。因此,对并非值的NULL使用比较谓词本来就是没有意义的。我们应当把IS NULL看作是一个谓词。
unkown、第三个真值
数据库采用了NULL而被引入的“第三个真值”。真值unknown和作为NULL的一种的UNKOWN(未知)是不同的东西。前者是明确的布尔型的真值,后者既不是值也不是变量。x是真值unknown时,x=x被判断为true,而x是UNKOWN时,x=x被判断为unkown。
优先级高的真值会决定计算结果。例如true AND unkown,因为unkown的优先级更高,所以结果时unkown。而ture OR unknown 的话,因为true优先级更高,所以结果时true。记住这个顺序后就能更方便地进行三值逻辑运算了。
1、比较谓词和NULL(1):排中律不成立
“把命题和它的否命题听过‘或者’连接而成的命题全都是真命题”这个命题在二值逻辑中被称为排中律(law of excluded middle)。顾名思义,排中律就是指不认可中间状态,对命题真伪的判定黑白分明,时古典逻辑学的重要原理。
2、比较谓词和NULL(2):CASE表达式和NULL
--col_1为1时返回O、为NULL时返回X的CASE表达式?
CASE col_1
WHEN 1 THEN 'O'
WHEN NULL THEN 'X'
END
这个CASE表达式一定不会返回X。这是因为第二个WHEN子句时col_1 = NULL的缩写形式。正如大家所知,这个式子的真值永远时unknown。而且CASE表达式的判断方法与WHERE子句一样,认可真值为true的条件。正确表达式:
CASE
WHEN col_1=1 THEN 'O'
WHEN col_1 IS NULL THEN 'X'
END
3、NOT IN 和 NOT EXISTS 不是等价的
--1.执行子查询,获取年龄列表
SELECT * FROM Class_A WHERE age NOT IN (22,23,NULL);
--2.用NOT和IN等价改写NOT IN
SELECT * FROM Class_A WHERE NOT age IN (22,23,NULL);
--3.用OR等价改写谓词IN
SELECT * FROM Class_A WHERE NOT b.((age=22)or(age=23)or(age = NULL));
--4.使用德·摩根定律等价改写
SELECT * FROM Class_A WHERE NOT (age=22) AND NOT (age=23) AND NOT (age = NULL);
--5.使用<>等价改写NOT和 =
SELECT * FROM Class_A WHERE (age<>22) AND (age<>23) AND (age <> NULL);
--6.对NULL使用<>后,结果为unknown
SELECT * FROM Class_A WHERE (age<>22) AND (age<>23) AND unknown ;
--7.如果AND运算里包含unknown,则结果不为true
SELECT * FROM Class_A WHERE false 或 unknown ;
如果在NOT IN子查询用到的表已选列中存在NULL,则SQL语句整体的查询结果永远都是空。为了得到正确的结果,我们需要使用EXISTS谓词。
SELECT * FROM Class_A A WHERE NOT EXISTS (SELECT * FROM Class_B B WHERE A.age=B.age and B.city = '东京');
同样地,我们再来一步一步地看着这段SQL是如何处理年龄为NULL的行的。
--1.在子查询里和NULL进行比较运算
SELECT * FROM Class_A A WHERE NOT EXISTS (SELECT * FROM Class_B B WHERE A.age=NULL and B.city = '东京');
--2.对NULL使用“=”后,结果为unkown
SELECT * FROM Class_A A WHERE NOT EXISTS (SELECT * FROM Class_B B WHERE unknown and B.city = '东京');
--3.如果AND运算里包括unkown,结果不会是true
SELECT * FROM Class_A A WHERE NOT EXISTS (SELECT * FROM Class_B B WHERE false 或 unknown ;);
--4.子查询没有返回结果,因此相反地,NOT EXISTS为true
SELECT * FROM Class_A A WHERE true);
产生这样的结果,是因为EXISTS谓词永远不会返回unknown。EXISTS可以互相替换使用,而NOT IN 和NOT EXISTS却不可以互相替换的混乱现象。
4、限定谓词和NULL
SQL里有ALL和ANY两个限定谓词。因为ANY与IN是等价的,所以人们不经常使用ANY。
--查询比B班住在东京的所有学生年龄都小的A班学生
SELECT * FROM Class_A A WHERE age < ALL (SELECT age FROM Class_B WHERE city = '东京');
ALL谓词其实是多个以AND连接的逻辑表达式的省略写法。
--1.执行子查询获取年龄列表
SELECT * FROM Class_A WHERE age < ALL (22,23,NULL) ;
--2.将ALL谓词等价改写为AND
SELECT * FROM Class_A WHERE (age < 22) AND (age < 23) AND (age < NULL) ;
--3.将NULL使用"<"后,结果变为unknown
SELECT * FROM Class_A WHERE (age < 22) AND (age < 23) AND unknown ;
--4.如果AND运算里包括Unknown,则结果不为true
SELECT * FROM Class_A WHERE false 或 unknown ;
5、限定谓词和极值函数不是等价的
--查询比B班住在东京的年龄最小的学生还要小的A班学生
SELECT * FROM Class_A WHERE age < (SELECT MIN(age) FROM Class_B WHERE city = '东京') ;
极值函数在统计时会把NULL的数据排除掉。使用极值函数能使Class_B这张表看起来就像不存在NULL一样。
ALL谓词:他的年龄比在东京住的所有学生都小。
极值函数:他的年龄比在东京住的年龄最小的学生还要小。
在现实世界中,这两个命题是一个意思。但是,正如我们通过前面的例题看到的那样,表里存在NULL时他们是不等价的;其实还有一种情况下它们是不等价的,谓词(或者函数)的输入为空集的情况。极值函数在输入为空表(空集)时会返回NULL。
--1.极值函数返回NULL
SELECT * FROM Class_A WHERE age < NULL ;
--2.对NULL使用"<"后结果为unknown
SELECT * FROM Class_A WHERE unknown ;
比较对象原本就不存在时,根据业务需求有时需要返回所有行,有时需要返回空集。需要返回所有行时,需要使用ALL谓词,或者使用COALESCE函数将极值函数返回到NULL处理成合适的值。
6、聚合函数和NULL
实际上,当输入为空表时返回NULL的不只是极值函数,COUNT以外的聚合函数也是如此。
--查询比住在东京都学生的平均年龄还要小的A班学生的SQL语句?
SELECT * FROM Class_A WHERE age < (SELECT AVG(age) FROM Class_B WHERE city = '东京') ;
没有住在东京都学生时,AVG函数会返回NULL。因此,外侧的WHERE子句永远是unknown,也就查询不到行。这种情况的解决方法只有两种:要么把NULL改写成具体值,要么闭上眼睛接收NULL。
本节小结
1、NULL不是值。
2、因为NULL不是值,所以不能对其使用谓词。
3、对NULL使用谓词后的结果是unknown。
4、unknown参与到逻辑运算时,SQL的运行会和预想的不一样。
5、按步骤追踪SQL的执行过程能有效应对4中的情况。
最后说明一下,要想解决NULL带来的各种问题,最佳方法应该是往表里添加NOT NULL 约束以尽力排除NULL。
字符串和NULL
NULL与空字符是不一样的
SQL中处理NULL的原则:NULL既不是值也不是变量,它只是一个表示“没有值”的标记。因此,NULL的运算处理与数值或字符串是不一样的。
--字符串与空字符连接后,结果没有变化
SELECT 'ABC'||'' AS string;
--字符串与NULL连接后,结果是NULL
SELECT 'ABC'|| NULL AS string;
空字符在页面上看不到的,但它确实是存在的字符串,只是长度为0而已。换句话说,它就相当于数值0.因此,如果将字符串链接的运算符比作“加法”,那么空字符就起着单位元的作用,字符串与空字符的连接就好比四则运算中的a+0=a。即使运算对象左右交换,结果也不会发生变化,这就是单位元的性质。由于NULL既不是字符串也不是任何类型的值,所以其运算结果是NULL。要想避免出现这种结果,我们就需要事先使用COALESCE函数将NULL转换为空字符。
任何原则都存在例外
实际上,关于上述原则介绍的处理,除了标准SQL中进行了相关规定以外,大部分DBMS也遵循该规则,但有一个DBMS存在例外,那就是ORACLE。
我们看一下Oracle中,将字符串与空字符或NULL进行连接时,会出现什么样的结果。
--字符串与空字符连接(Oracle),结果没有变化
SELECT 'ABC'||'' AS string;
--字符串与NULL连接(Oracle),结果没有变化
SELECT 'ABC'|| NULL AS string;
关于字符串和NULL,Oracle中有如下3个规则:
1、原则上将空字符作为NULL进行处理。
2、不过,仅在进行字符串连接时,才将NULL视为空字符。
3、进一步来讲,仅在字符串连接的2个运算对象都是NULL的情况下才将它们视为NULL(等同于结果是NULL)。




