SQL进阶_3

2023-10-26

三值逻辑和 NULL

大多数编程语言都是基于二值逻辑的,即逻辑真值只有真和假两个。而 SQL 语言则采用一种特别的逻辑体系——三值逻辑,即逻辑真值除了真和假,还有第三个值“不确定”。

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

理论篇

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

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

“未知”指的是“虽然现在不知道,但加上某些条件后就可以知道”;而“不适用”指的是“无论怎么努力都无法知道”。

为什么必须写成“IS NULL”,而不是“= NULL”?

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

为了便于记忆,请注意这三个真值之间有下面这样的优先级顺序。

 AND 的情况:false > unknown > true 

● OR 的情况:true > unknown > false

优先级高的真值会决定计算结果。例如 true AND unknown,因为unknown的优先级更高,所以结果是 unknown。而true OR unknown的话,因为 true 优先级更高,所以结果是 true。

实践篇

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

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

“把命题和它的否命题通过‘或者’连接而成的命题全都是真命题”这个命题在二值逻辑中被称为

排中律(Law of Excluded Middle)。

如果排中律在 SQL 里也成立,那么下面的查询应该能选中表里的所有行。

-- 查询年龄是 20 岁或者不是 20 岁的学生
SELECT *
 FROM Students
 WHERE age = 20
 OR age <> 20;

遗憾的是,在 SQL 的世界里,排中律是不成立的。假设表 Students里的数据如下所示。

关于这个原因,我们在理论篇里学习过,即对 NULL 进行比较运算的结果是unknown。

具体来说,约翰这一行是按照下面的步骤被判断的。

-- 1. 约翰年龄是 NULL(未知的 NULL !
SELECT *
 FROM Students
 WHERE age = NULL
 OR age <> NULL;
-- 2. 对 NULL 使用比较谓词后,结果为 unknown
SELECT *
 FROM Students
 WHERE unknown
 OR unknown;
-- 3.unknown OR unknown 的结果是 unknown(参考“理论篇”中的矩阵)
SELECT *
 FROM Students
 WHERE unknown;

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

2. 比较谓词和 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

3. NOT IN 和 NOT EXISTS 不是等价的

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

Class_A

 

Class_B 

需求:查询与 B 班住在北京的学生年龄不同的 A 班学生

用in 实现的sql

select * from Class_A where age not in (select age from Class_B where city = '北京')

查询结果

空空如也!这是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 谓词。

select * from Class_A a where 
                                not exists
         (select 1 from Class_B b where a.age = b.age and b.city = '北京');

查询结果 

分析流程

select * from Class_A a where not exists (select 1 from Class_B b where a.age = b.age and b.city = '北京');

-- 1. 在子查询里和 NULL 进行比较运算
select * from Class_A a where not exists (select 1 from Class_B b where a.age = null and b.city = '北京');

-- 2. 对 NULL 使用“=”后,结果为 unknown
select * from Class_A a where not exists (select 1 from Class_B b where unknown and b.city = '北京');

-- 3. 如果 AND 运算里包含 unknown,结果不会是 true
select * from Class_A a where not exists (select 1 from Class_B b where unknown or false);

-- 4. 子查询没有返回结果,因此相反地,NOT EXISTS 为 true
select * from Class_A a where true;

因为 EXISTS 谓词永远不会返回 unknown。EXISTS 只会返回 true 或者 false。因此就有了IN 和 EXISTS 可以互相替换使用,而 NOT IN 和 NOT EXISTS 却不可以互相替换的混乱现象。

4. 限定谓词和 NULL

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

Class_A

Class_B  

需求:比B 班住在北京的所有学生年龄都小的 A 班学生

用all 实现的sql

select * from Class_A 
                where
             age < all(select age from Class_B b where b.city = '北京')

实现结果 

查询到的只有比胡汉三小的李四,到这里都没有问题。但是如果胡汉三年龄不详,就会有问题了。凭直觉来说,此时查询到的可能是比 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;

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

使用极值函数写上述的sql

select * from Class_A 
            where 
        age < (select min(age) from Class_B b where b.city = '北京')

实现结果 

没有问题。即使胡汉三的年龄无法确定,这段代码也能查询到李四和王五两人。这是因为,极值函数在统计时会把为 NULL 的数据排除掉。

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

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

在现实世界中,这两个命题是一个意思。但是,正如我们通过前面的例题看到的那样,表里存在 NULL 时它们是不等价的。

其实还有一种情况下它们也是不等价的,大家知道是什么吗?

        答案是,谓词(或者函数)的输入为空集的情况。下图。

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

此时极值函数的执行顺序

-- 1. 极值函数返回 NULL
select * from Class_A where age < null;

-- 2. 对 NULL 使用“<”后结果为 unknown
select * from Class_A where unknown;

6. 聚合函数和 NULL

需求:

查询比住在北京的学生的平均年龄还要小的 A 班学生的 SQL 语句?

sql

select * from  Class_A where age < (select avg(age) from Class_B where city = '北京')

没有住在北京的学生时,AVG 函数返回 NULL。因此,外侧的 WHERE子句永远是 unknown,也就查询不到行。使用 SUM 也是一样。

要点!!!

1. NULL 不是值。

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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SQL进阶_3 的相关文章

随机推荐

  • [管理与领导-84]:IT基层管理者 - 核心技能 - 高效执行力 - 9 - 工作中处处是客户,高效执行力的方向:客户 服务 部门 上司 上下游终端 流程 输入 输出

    目录 前言 一 高效执行的努力方向 二 干系人 客户 上下游 部门 服务 干系人 广义的客户 企业内部客户 上下游 三 输入 需求 目标 评判标准 系统的输入 需求 需求规范 目标 评判标准 四 数据处理 研发过程 执行过程 数据处理 过程
  • redis详解(四)—— 高可用分布式集群

    一 高可用 高可用 High Availability 是当一台服务器停止服务后 对于业务及用户毫无影响 停止服务的原因可能由于网卡 路由器 机房 CPU负载过高 内存溢出 自然灾害等不可预期的原因导致 在很多时候也称单点问题 1 解决单点
  • 吐血分享!这几个在线网站超劲爆,福利满满

    现在网上资源丰富 有许多干货都是我们不知道的 小编今天和你们分享一些好的在线资源 今天就免费分享给大家 一 全集网 全集网主要是可以进行全网搜索下载电影或者电视剧的一个在线网站 并且更新的也很快哦 里面有好多好看的电影 喜欢看电视的小伙伴们
  • WPF VirtualizationMode属性Recycling和Standard的区别

    默认情况下 VirtualizingStackPanel 创建每个可见项的项容器以及丢弃 当不再需要时 例如 当项滚动到视图之外时 当 ItemsControl 包含许多项目时 会创建并放弃项容器处理会对性能产生负面影响 当 Virtual
  • Hadoop集群部署

    目录 1 部署规划 2 准备三台虚拟机 1 创建三个文件夹 然后把之前创建的虚拟机里面的文件分别复制到这三个文件夹中 编辑 2 用vmware分别打开这三个文件夹下的虚拟机 并分别重命名 3 修改这三台虚拟机的IP 1 运行master 然
  • Cocos2d 本地数据保存接口封装

    设置key value进行保存 setLocalStorageString function key value key LS key if typeof this key undefined undefined this key sys
  • 设计模式之装饰器模式

    装饰器模式 动态的将责任附加到对象上 若要扩展功能 装饰器提供了比继承更有弹性的替代方案 装饰器中有两个比较重要的角色 装饰者和被装饰者 被装饰者实现我们的核心逻辑 装饰者只是对这些逻辑进行增强 比如点一杯饮料 冰 加豆浆等都是装饰者 是对
  • 自旋锁与互斥锁的对比、手工实现自旋锁

    本文地址 LYanger的博客 http blog csdn net freeelinux article details 53695111 本文之前 我只是对自旋锁有所了解 知道它是做什么的 但是没有去测试实现过 甚至以为自旋锁只有ker
  • 顺序表的插入

    前面讲了关于顺序表的查找 这篇文章将讨论顺序表的插入与删除 首先来看看插入操作 在第i个位置插入一个新元素 使长度为n的线性表变为长度为n 1的线性表 我们在前一篇文章结尾谈到插入与删除需要移动大量的元素 其实就是移动原表的一些位置的结点
  • java - file文件操作

    目录 1 文件属性查询操作 2 创建 txt文件 3 删除 txt文件 4 创建文件夹 5 删除文件夹 6 获取当前目录下所有文件夹的名称 7 文件过滤 1 文件属性查询操作 package file import java io File
  • 一个注解搞定Spring Security 忽略拦截

    Copyright 2021 2022 the original author or authors Licensed under the Apache License Version 2 0 the License you may not
  • day009:HTML表单标签(2)

    接上节
  • 蓝桥杯青少组省赛Python模拟题

    请你编写程序 输入一串数字 输出这串数字的总个数 输入描述 输入一行 内容为数字 输出描述 输出一个正整数 表示输入数字的总个数 样例输入 6789 样例输出 4 代码 n input print len n 已知1小时等于60分钟 1分钟
  • 不上架App Store怎么安装到非越狱苹果手机使用

    很多开发者打包了ipa却不能安装 因为ios不像安卓打包出来就能安装 如苹果手机没越狱 需要提供特定的ios证书打包的ipa才能安装到手机使用 有两种ios证书可以实现 1 ios开发者证书 个人和公司账号申请 需要添加udid 限制100
  • (c语言)单、双精度

    单精度型 1 用符号float表示 2 分配4个字符 3 数值范围3 4E 38 3 4E 38 提供七个有效数字 4 后缀为f或F表示单精度浮点型 双精度型 1 用符号double表示 2 分配8个字符 3 数值范围1 7E 308 1
  • python安装包失败 ModuleNotFoundError: No module named 'lxml'

    最近在学习python 用到了tushare这个包 但是安装的时候 总是报 import lxml html ModuleNotFoundError No module named lxml Command python setup py
  • Matplotlib绘制象限图——支持中文、箭头、自定义轴线交点

    Matplotlib绘制象限图 支持中文 箭头 自定义轴线交点 1 效果图 2 原理 2 1 绘制象限图步骤 2 1 添加文字到图表 3 源码 参考 这篇博客将介绍如何使用matplotlib绘制象限图 写这篇博客源于博友的提问 首先pye
  • GDB first impression

    I never use GDB before though I ve heard of it long times ago Well I never use it simply because I never really debug pr
  • 利用 DAC0832 设计信号发生器

    Proteus 仿真要求 利用 DAC0832 产生一个占空比为 1 3 的矩形波信号 电路图 代码图 利用 DAC0832 产生一个占空比为 1 3 的矩形波信号 include
  • SQL进阶_3

    三值逻辑和 NULL 大多数编程语言都是基于二值逻辑的 即逻辑真值只有真和假两个 而 SQL 语言则采用一种特别的逻辑体系 三值逻辑 即逻辑真值除了真和假 还有第三个值 不确定 普通语言里的布尔型只有 true 和 false 两个值 这种