【SQL 中级语法 3】三值逻辑和NULL

news2024/12/23 13:58:19

普通语言里的布尔型只有true和false两个值,这种逻辑体系被称为二值逻辑。而SQL语言里,除此之外还有第三个值unknown,因此这种逻辑体系被称为三值逻辑(three-valued logic)。

为什么SQL语言采用了三值逻辑呢?

问题的答案就在于NULL。关系数据库里引进了NULL,所以不得不同时引进第三个值。这样的三值逻辑一次次地违背常识,深深地困扰着数据库工程师们。

目录

1 理论篇

1.1 两种NULL、三值逻辑还是四值逻辑

1.2 为什么必须写成 ’IS NULL‘ ,而不是 ’= NULL‘

1.3 unknown 第三个真值

1.4 三值逻辑的真值表

2 实践篇

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

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

2.3 NOT IN 和 NOT EXISTS不是等价的

2.4 限定谓词和 NULL

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

2.6 聚合函数 和 NULL


1 理论篇

1.1 两种NULL、三值逻辑还是四值逻辑

SQL里只存在一种NULL。然而在讨论NULL时,我们一般都会将它分成两种类型来思考。因此这里先来介绍一些基础知识,即两种NULL之间的区别。

两种NULL分别指的是“未知”(unknown)和“不适用”(not applicable,inapplicable)。以“不知道戴墨镜的人眼睛是什么颜色”这种情况为例,这个人的眼睛肯定是有颜色的,但是如果他不摘掉眼镜,别人就不知道他的眼睛是什么颜色。这就叫作未知。而“不知道冰箱的眼睛是什么颜色”则属于“不适用”。因为冰箱根本就没有眼睛,所以“眼睛的颜色”这一属性并不适用于冰箱。(这个例子举的多少有点本土不服了哈哈哈)

“冰箱的眼睛的颜色”这种说法和“圆的体积”“男性的分娩次数”一样,都是没有意义的。平时,我们习惯了说“不知道”,但是“不知道”也分很多种。“不适用”这种情况下的NULL,在语义上更接近于“无意义”,而不是“不确定”。这里总结一下:“未知”指的是“虽然现在不知道,但加上某些条件后就可以知道”;而“不适用”指的是“无论怎么努力都无法知道”。

 Codd曾经认为应该严格地区分两种类型的NULL,并提倡在关系数据库中使用四值逻辑[插图]。不知道是幸运还是不幸(笔者认为肯定是幸运),他的这个想法并没有得到广泛支持,现在所有的DBMS都将两种类型的NULL归为了一类并采用了三值逻辑。但是他的这种分类方法本身还是有很多优点的,因此后来依然有很多学者支持。

(具体的四值逻辑判断略)

1.2 为什么必须写成 ’IS NULL‘ ,而不是 ’= NULL‘

对NULL使用比较谓词后得到的结果总是unknown。而查询结果只会包含WHERE子句里的判断结果为true的行,不会包含判断结果为false和unknown的行。不只是等号,对NULL使用其他比较谓词,结果也都是一样的。所以无论col_1是不是NULL,比较结果都是unknown。

    --以下的式子都会被判为 unknown
    1 = NULL
    2 > NULL
    3 < NULL
    4 <> NULL
    NULL = NULL

那么,为什么对NULL使用比较谓词后得到的结果永远不可能为真呢?这是因为,NULL既不是值也不是变量。NULL只是一个表示“没有值”的标记,而比较谓词只适用于值。因此,对并非值的NULL使用比较谓词本来就是没有意义的注。

所以,牢记,NULL并不是值;

“列的值为NULL”“NULL值”这样的说法本身就是错误的。因为NULL不是值,所以不在定义域(domain)中。相反,如果有人认为NULL是值,那它是什么类型的值?关系数据库中存在的值必然属于某种类型,比如字符型或数值型等。所以,假如NULL是值,那么它就必须属于某种类型。

NULL容易被认为是值的原因恐怕有两个。第一个是在C语言等编程语言里面,NULL被定义为了一个常量(很多语言将其定义为了整数0),这导致了人们的混淆。但是,其实SQL里的NULL和其他编程语言里的NULL是完全不同的东西(请参考本节末尾参考文献中的“C语言初级Q&A”)。

第二个原因是,IS NULL这样的谓词是由两个单词构成的,所以人们容易把IS当作谓词,而把NULL当作值。特别是SQL里还有IS TRUE、IS FALSE这样的谓词,人们由此类推,从而这样认为也不是没有道理。但是正如讲解标准SQL的书里提醒人们注意的那样,我们应该把IS NULL看作是一个谓词。因此,如果可以的话,写成IS_NULL这样也许更合适。

1.3 unknown 第三个真值

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

为了让大家理解两者的不同,我们来看一个x=x这样的简单等式。x是真值unknown时,x=x被判断为true,而x是UNKNOWN时被判断为unknown。

    --这个是明确的真值的比较
    unknown = unknown → true

    --这个相当于NULL = NULL
    UNKNOWN = UNKNOWN → unknown

1.4 三值逻辑的真值表

 

 

 还是比较好记的,优先级上看:

● AND的情况: false > unknown > true

● OR的情况: true > unknown > false

2 实践篇

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

我们假设约翰是一个人。那么,下面的语句(以下称为“命题”)是真是假?

约翰是20岁,或者不是20岁,二者必居其一。——P

在SQL的世界里排中律是不成立的

    --添加第3个条件:年龄是20岁,或者不是20岁,或者年龄未知
    SELECT *
      FROM Students
     WHERE age = 20
        OR age <> 20
        OR age IS NULL;

实际上约翰这个人是有年龄的,只是我们无法从这张表中知道而已。

换句话说,关系模型并不是用于描述现实世界的模型,而是用于描述人类认知状态的核心(知识)的模型。因此,我们有限且不完备的知识也会直接反映在表里。(这句话看起来好有深意)

即使不知道约翰的年龄,他在现实世界中也一定“要么是20岁,要么不是20岁”——我们容易自然而然地这样认为。然而,这样的常识在三值逻辑里却未必正确。

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

在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

(经典!)

这里请再次确认自己已经记住“NULL并不是值”这点!

2.3 NOT IN 和 NOT EXISTS不是等价的

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

 请注意,B班山田的年龄是NULL。我们考虑一下如何根据这两张表查询“与B班住在东京的学生年龄不同的A班学生”。也就是说,希望查询到的是拉里和伯杰。因为布朗与齐藤年龄相同,所以不是我们想要的结果。如果单纯地按照这个条件去实现,则SQL语句如下所示。

    --查询与B班住在东京的学生年龄不同的A班学生的SQL语句?
    SELECT *
      FROM Class_A
     WHERE age NOT IN ( SELECT age
                          FROM Class_B
                        WHERE city =’东京’);

这条SQL语句查询不到任何数据。

如果山田的年龄不是NULL(且与拉里和伯杰年龄不同),是能顺利找到拉里和伯杰的。

    --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;

这里对A班的所有行都进行了如此繁琐的判断,然而没有一行在WHERE子句里被判断为true。也就是说,如果NOT IN子查询中用到的表里被选择的列中存在NULL,则SQL语句整体的查询结果永远是空。(我的天!)

想得到正确的结果,需要用EXISTS

    --正确的SQL语句:拉里和伯杰将被查询到
    SELECT *
      FROM Class_A  A
     WHERE NOT EXISTS ( SELECT *
                          FROM Class_B B
                        WHERE A.age = B.age
                          AND B.city = ’东京’);

执行结果 

    name   age   city
    -----  ----  ----
    拉里    19    埼玉
    伯杰    21    千叶

来看下使用EXISTS 对 NULL 来说与 IN 的不同。

    --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;

也就是说,山田被作为“与任何人的年龄都不同的人”来处理了(但是,还要把与年龄不是NULL的齐藤及田尻进行比较后的处理结果通过AND连接,才能得出最终结果)。产生这样的结果,是因为EXISTS谓词永远不会返回unknown。EXISTS只会返回true或者false。因此就有了IN和EXISTS可以互相替换使用,而NOT IN和NOT EXISTS却不可以互相替换的混乱现象。虽然写代码的时候很难做到绝对不依赖直觉,但作为数据库工程师来说,还是需要好好理解一下这种现象。

2.4 限定谓词和 NULL

SQL里有ALL和ANY两个限定谓词。因为ANY与IN是等价的,所以我们不经常使用ANY。在这里,我们主要看一下更常用的ALL的一些注意事项。

ALL可以和比较谓词一起使用,用来表达“与所有的××都相等”,或“比所有的××都大”的意思。接下来,我们给B班表里为NULL的列添上具体的值。然后,使用这张新表来思考一下用于查询“比B班住在东京的所有学生年龄都小的A班学生”的SQL语句。

 使用ALL谓词时,SQL语句可以像下面这样写。

    --查询比B班住在东京的所有学生年龄都小的A班学生
    SELECT *
      FROM Class_A
     WHERE age < ALL ( SELECT age
                        FROM Class_B
                        WHERE city =’东京’);

执行结果

    name   age   city
    -----  ----  ----
    拉里    19     埼玉

查询到的只有比山田年龄小的拉里,到这里都没有问题。但是如果山田年龄不详,就会有问题了。凭直觉来说,此时查询到的可能是比22岁的齐藤年龄小的拉里和伯杰。然而,这条SQL语句的执行结果还是空。这是因为,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;

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

    --查询比B班住在东京的年龄最小的学生还要小的A班学生
    SELECT *
      FROM Class_A
     WHERE age < ( SELECT MIN(age)
                    FROM Class_B
                    WHERE city =’东京’);

 执行结果

    name   age   city
    -----  ----  ----
    拉里    19    埼玉
    伯杰    21    千叶

没有问题。即使山田的年龄无法确定,这段代码也能查询到拉里和伯杰两人。这是因为,极值函数在统计时会把为NULL的数据排除掉。使用极值函数能使Class_B这张表里看起来就像不存在NULL一样。

● ALL谓词:他的年龄比在东京住的所有学生都小——Q1

● 极值函数:他的年龄比在东京住的年龄最小的学生还要小——Q2

这两个命题,在有NULL时不是等价的,同时谓词(或者函数)的输入为空集时,这两个命题也不等价。

若Class_B没有住在东京的学生!

 B班里没有学生住在东京。这时,使用ALL谓词的SQL语句会查询到A班的所有学生。然而,用极值函数查询时一行数据都查询不到。这是因为,极值函数在输入为空表(空集)时会返回NULL。因此,使用极值函数的SQL语句会像下面这样一步步被执行。

    --1.极值函数返回NULL
    SELECT *
      FROM Class_A
     WHERE age < NULL;

    --2.对NULL使用“<”后结果为 unknown
    SELECT *
      FROM Class_A
     WHERE unknown;

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

2.6 聚合函数 和 NULL

实际上,当输入为空表时返回NULL的不只是极值函数,COUNT以外的聚合函数也是如此。

    --查询比住在东京的学生的平均年龄还要小的A班学生的SQL语句?
    SELECT *
      FROM Class_A
     WHERE age < ( SELECT AVG(age)
                    FROM Class_B
                    WHERE city =’东京’);

没有住在东京的学生时,AVG函数返回NULL。因此,外侧的WHERE子句永远是unknown,也就查询不到行。使用SUM也是一样。这种情况的解决方法只有两种:要么把NULL改写成具体值,要么闭上眼睛接受NULL。但是如果某列有NOT NULL约束,而我们需要往其中插入平均值或汇总值,那么就只能选择将NULL改写成具体值了。

聚合函数和极值函数的这个陷阱是由函数自身带来的,所以仅靠为具体列加上NOT NULL约束是无法从根本上消除的。因此我们在编写SQL代码的时候需要特别注意。

小结

1.NULL不是值。

2.因为NULL不是值,所以不能对其使用谓词。

3.对NULL使用谓词后的结果是unknown。

4.unknown参与到逻辑运算时,SQL的运行会和预想的不一样。

5.按步骤追踪SQL的执行过程能有效应对4中的情况。

最后说明一下,要想解决NULL带来的各种问题,最佳方法应该是往表里添加NOT NULL约束来尽力排除NULL。这样就可以回到美妙的二值逻辑世界(虽然并不能完全回到)

(这章比上一节理解的好多了!)

———————————————————————————————————————————

更多文章点击主页查看~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/14555.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java项目-第139期ssm博客系统-ssm毕业设计_计算机毕业设计

java项目-第139期ssm博客系统-ssm毕业设计_计算机毕业设计 【源码请到资源专栏下载】 今天分享的项目是《ssm博客系统》 该项目分为前台和后台2个部分。 前台不需要登录&#xff0c;游客都可以访问&#xff0c;并发表评论 管理员登录后可以进行文章的发表&#xff0c;分类&…

德鲁克《卓有成效的管理者》学习读书-总结

有幸学习了管理大师德鲁克先生的《卓有成效的管理者》&#xff0c;帮助学习者理清了在理论和实践之间建立桥梁&#xff0c;使其生根落地&#xff0c;开花结果&#xff1b;管理不是常识&#xff0c;管理是个实践学科&#xff0c;要不断温习&#xff0c;不断与领导同事联接&#…

人肠道宏病毒与其宿主和环境因素的关联分析

近期《Nature Communications》期刊上(IF17.694)发表的“Extensive gut virome variation and its associations with host and environmental factors in a population-level cohort”研究论文中&#xff0c;对从4198个个体的肠道宏基因组中获得的人类肠道病毒进行分析&#x…

PCB设计时如何选择合适的叠层方案

大家在画多层PCB的时候都要进行层叠的设置&#xff0c;其中层数越多的板子层叠方案也越多&#xff0c;很多人对多层PCB的层叠不够了解&#xff0c;通常一个好的叠层方案可以降低板子产生的干扰&#xff0c;我们的层叠结构是影响PCB板EMC性能的重要因素,下面我们以四层板和六层板…

刨根问底 Redis, 面试过程真好使

充满寒气的互联网如何在面试中脱颖而出&#xff0c;平时积累很重要&#xff0c;八股文更不能少&#xff01;下面带来的这篇 Redis 问答希望能够在你的 offer 上增添一把&#x1f525;。 在 Web 应用发展的初期阶段&#xff0c;一个网站的访问量本身就不是很高&#xff0c;直接使…

SRM供应商关系管理系统解决方案

SRM供应商关系管理系统解决方案供应商关系管理(SRM)软件的采购指南 什么是供应商关系管理(SRM)软件? 供应商关系管理(SRM)软件是一个通讯解决方案制造商、分销商和零售商的供应链。供应商管理用于将所有有关组织的供应商通讯和颜色编码索引卡片。现在SRM管理的数字由一个国家…

OOM内存溢出分析

Mat内存溢出dump文件分析工具http://www.eclipse.org/mat/downloads.php 模拟OOM Java 程序 package org.cj.oom;import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;/*** 创建内存分析* java启动参数指定内存 -Xms1m -Xmx1m* author…

没有上司的舞会 - 树形DP

目录题目描述前言C代码题目描述 Ural 大学有 NNN 名职员&#xff0c;编号为 1∼N1∼N1∼N。 他们的关系就像一棵以校长为根的树&#xff0c;父节点就是子节点的直接上司。 每个职员有一个快乐指数&#xff0c;用整数 HiH_iHi​ 给出&#xff0c;其中 1≤i≤N1≤i≤N1≤i≤N。…

ElasticSearch分布式搜索引擎安装保姆级教程

ElasticSearch分布式搜索引擎安装教程 一.Hr&#xff1a;ElasticSearch是什么&#xff1f; 答&#xff1a;Elasticsearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎&#xff0c;基于RESTful web接口。Elasticsearch是用Java语言开发的&#…

后端API接口性能优化的10种方案,真有用!

后端API接口性能优化的10种方案&#xff0c;真有用&#xff01; 批量思想&#xff1a;批量操作数据库 优化前&#xff1a; //for循环单笔入库 for(TransDetail detail:transDetailList){insert(detail); } 优化后&#xff1a; batchInsert(transDetailList); 打个比喻&…

docker-compose的安装与卸载

compose项目是docker官方的开源项目,负责实现对Docker容器的快速编排 定位 定位是定义与运行多个docker容器的应用,同时可以对多个容器进行编排Compose 中有两个重要的概念&#xff1a; 服务 (service)&#xff1a;一个应用的容器&#xff0c;实际上可以包括若干运行相同镜像的…

电压跌落检测

1.前言 根据国际上较为权威的 IEEE 制定的标准可知&#xff0c;电压跌落是指电力系统中某点工频电压方均根值突然降低至 0.1p.u. ~ 0.9p.u.&#xff0c;并在短暂持续10ms~1min 后恢复正常的现象。在对电压跌落进行检测时一般都需要检测起止时刻、相位跳变、跌落深度这三个特征…

小啊呜产品读书笔记001:《邱岳的产品手记-03》第04+05讲 如何当好AI时代的产品经理 06讲 产品案例分析·TheGuardian的文本之美

小啊呜产品读书笔记001&#xff1a;《邱岳的产品手记-03》第0405讲 如何当好AI时代的产品经理 & 06讲 产品案例分析TheGuardian的文本之美一、今日阅读计划二、泛读&知识摘录1、04讲 如何当好AI时代的产品经理&#xff1f;&#xff08;学习篇&#xff09;2、05讲 如何当…

07-服务管理-02-yum常用命令和yum源

文章目录1. yum常用命令1.1 安装卸载服务- 安装- 升级- 卸载- 清空缓存- 只下载不安装1.2 查看- 查看yum库- 列出所有包- 查看包信息- 查看文件所在包1.3 包组的使用2. 指定yum源3. 常用yum源1&#xff09;epel源2&#xff09;163源3&#xff09;阿里源4&#xff09;清华源4. y…

盘点实验室的----FITC-透明质酸;FITC-HA不同透明质酸分子量修饰

FITC(异硫氰酸荧光素)是一种胺活性衍生物的荧光染料&#xff0c;FITC 具有广泛的应用&#xff0c;作为抗体和其他探针标记&#xff0c;用荧光显微镜&#xff0c;流式细胞仪、免疫荧光方法如 ELISA 和 Western印迹实验。异硫氰酸荧光素(FITC)具有永久性标记生物分子的独特检测性…

【C++】构造函数、析构函数、拷贝构造函数

目录 构造函数 析构函数 拷贝构造函数 C 在 C 语言的基础上增加了面向对象编程&#xff0c;C 支持面向对象程序设计。类是 C 的核心特性&#xff0c;通常被称为用户定义的类型。 类用于指定对象的形式&#xff0c;它包含了数据表示法和用于处理数据的方法。类中的数据和方…

Nginx下载安装以及简单使用

本文来说下Nginx的下载安装以及常用功能的简单使用 文章目录下载nginx启动nginx关闭nginx下载nginx 下载地址:http://nginx.org/en/download.html 直接下载 nginx-1.18.0.zip&#xff0c;下载后解压&#xff0c;解压后如下 启动nginx 有很多种方法启动nginx (1)直接双击nginx.…

浅谈深度学习中的概率

摘要&#xff1a;本次就和大家聊一聊深度学习中的概率。本文分享自华为云社区《【MindSpore易点通】深度学习中的概率》&#xff0c;作者&#xff1a; chengxiaoli。 为什么会用到概率呢&#xff1f;因为在深度学习中经常会需要处理随机的数据&#xff0c;或者包含随机性的任务…

Java基础之《netty(2)—IO模型、BIO介绍、NIO介绍》

一、IO模型 1、I/O模型简单的理解&#xff1a;就是用什么样的通道进行数据的发送和接收&#xff0c;很大程度上决定了程序通信的性能。 2、Java共支持3种网络编程模型I/O模式&#xff1a;BIO、NIO、AIO。 3、Java BIO&#xff1a;同步并阻塞&#xff08;传统阻塞型&#xff…

DeepCTR:易用可扩展的深度学习点击率预测算法包

这个项目主要是对目前的一些基于深度学习的点击率预测算法进行了实现&#xff0c;如PNN,WDL,DeepFM,MLR,DeepCross,AFM,NFM,DIN,DIEN,xDeepFM,AutoInt等,并且对外提供了一致的调用接口。 关于每种算法的介绍这里就不细说了&#xff0c;大家可以看论文&#xff0c;看知乎&#x…