MICK-SQL进阶教程 1.3 三值逻辑和NULL

让自己愈发觉得自己学艺不精的一章、、、

要点:

  • 三值逻辑:true, false, unknown

  • 必须写成“IS NULL”,而不是“= NULL”:对 NULL 使用比较谓词后得到的结果总 是 unknown。而查询结果只会包含 WHERE 子句里的判断结果为 true 的行, 不会包含判断结果为 falseunknown 的行。

  • NULL 既不是值也不是变量。NULL 只是一个表示“没有值”的 标记,而比较谓词只适用于值。因此,对并非值的 NULL 使用比较谓词本来就是没有意义的 。

    真值 unknown 和作为 NULL 的一种的 UNKNOWN(未知)是不同的东西。前者是明确的布尔型的真值,后者既不是值也不 是变量。为了便于区分,前者采用粗体的小写字母 unknown,后者用普通 的大写字母 UNKNOWN 来表示。

  • 真值的优先级排序

    • AND 的情况: false > unknown > true
    • OR 的情况: true > unknown > false

比较谓词和 NULL(1) :排中律不成立

比较谓词和 NULL(2) :CASE 表达式和 NULL

-- col_1 为 1 时返回○、为 NULL 时返回 × 的 CASE 表达式? 
CASE col_1
WHEN 1 THEN '○'
WHEN NULL THEN '×' END
-- 这个 CASE 表达式一定不会返回 ×。这是因为,第二个 WHEN 子句 是 col_1 = NULL 的缩写形式。正如大家所知,这个式子的真值永 远是 unknown。而且 CASE 表达式的判断方法与 WHERE 子句一样,只 认可真值为 true 的条件。
-- 正确的写法是像下面这样使用搜索 CASE 表 达式。
CASE WHEN col_1 = 1 THEN '○' 
         WHEN col_1 IS NULL THEN '×'
END

NOT IN 和 NOT EXISTS 不是等价的

在对 SQL 语句进行性能优化时,经常用到的一个技巧是将 IN 改写成 EXISTS。这是等价改写,并没有什么问题。问题在于,将 NOT IN 改写 成NOT EXISTS时,结果未必一样。

-- 选择“与 B 班(class_b,包含空值)住在东京的学生年龄不同的 A 班(class_a)学生”
-----------------------------------------------------
-- 错误写法
select name
from class_a
where age not in (select age 
                  in class_b 
                  where city = '东京');
-- 结果:空
-----------------------------------------------------
-- 执行原理
  --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 ( (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 语句整体的查询结果永远是空。

-- 正确写法
select name
from class_a
where age not exists (select age 
                        in class_b 
                        where city = '东京');
-----------------------------------------------------
-- 原理
    --1. 在子查询里和NULL进行比较运算
    select *
  from Class_A A
  where not exists (select * 
                    from Class_B B 
                    where A.age = NULL 
                      and B.city = '东京');
  --2.对NULL使用“=”后,结果为 unknown
  SELECT *
  FROM Class_A A
  WHERE NOT EXISTS (SELECT *
                                    FROM Class_B B 
                    WHERE unknown AND B.city = '东京')
  --3. 如果 AND 运算里包含 unknown,结果不会是 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 只会返回 true 或者 false。因此就有了 IN 和 EXISTS 可以互相替换使用,而 NOT IN 和 NOT EXISTS 却不可以互 相替换的混乱现象。

限定谓词和 NULL

SQL 里有 ALL 和 ANY 两个限定谓词。因为 ANY 与 IN 是等价的,所以 我们不经常使用 ANY。ALL 可以和比较谓词一起使用,用来表达“与所有的 ×× 都相等”, 或“比所有的 ×× 都大”的意思。

-- 查询比 B 班住在东京的所有学生年龄都小的 A 班学生
-----------------------------------------------------
-- b表中不含Null时
SELECT *
FROM Class_A
WHERE age < ALL ( SELECT age
                  from Class_B
                  WHERE city = '东京')
-- 结果:正确
-----------------------------------------------------
-- b表中含Null时
-- 结果:空
-- 原理: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;

限定谓词和极值函数不是等价的

  • 极值函数在统计时会把为 NULL 的数据排除掉

  • 极值函数在输入为空表(空集)时会返回 NULL

ALL 谓词和极值函数表达的命题含义分别如下所示:

  • ALL 谓词:他的年龄比在东京住的所有学生都小
  • 极值函数:他的年龄比在东京住的年龄最小的学生还要小

命题不等价的情况:

  • 表里存在 NULL 时它们是不等价的

  • 谓词(或者函数)的输入为空集的情况(例如B表中没有学生住东京):

    这时,使用 ALL 谓词的 SQL 语句会查询到 A 班的所有学生。然而,用极值函数查询时一行数据都查询不到。因为极值函数在输入为空表(空集)时会返回 NULL

-- 查询比 B 班住在东京的年龄最小的学生还要小的 A 班学生 
SELECT *
FROM Class_A
WHERE age < ( SELECT MIN(age)
                            FROM Class_B
                            WHERE city = '东京' );
-- 原理
--1. 极值函数返回 NULL 
SELECT *
FROM Class_A 
WHERE age < NULL;
--2.对NULL使用“<”后结果为 unknown 
SELECT *
FROM Class_A 
WHERE unknown;

比较对象原本就不存在时,根据业务需求有时需要返回所有行,有时需要返回空集。需要返回所有行时(感觉这类似于“不战而胜”),需要使 用 ALL 谓词,或者使用 COALESCE 函数将极值函数返回的 NULL 处理成合 适的值。

聚合函数和NULL

聚合函数在输入为空表(空集)时会返回 NULL

-- 查询比住在东京的学生的平均年龄还要小的 A 班学生的 SQL 语句? 
SELECT *
FROM Class_A
WHERE age < ( SELECT AVG(age)
              FROM Class_B
              WHERE city = '东京' );
-- 没有住在东京的学生时,AVG 函数返回 NULL。

本节要点

  1. NULL 不是值。
  2. 因为 NULL 不是值,所以不能对其使用谓词。
  3. 对 NULL 使用谓词后的结果是 unknown
  4. unknown 参与到逻辑运算时,SQL 的运行会和预想的不一样。
  5. 按步骤追踪 SQL 的执行过程能有效应对 4 中的情况。

要想解决 NULL 带来的各种问题,最佳方法应该是往 表里添加NOT NULL约束来尽力排除NULL。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269