SQL进阶系列之3三值逻辑与NULL
写在前面
普通编程语言里的布尔型只有true和false两个值,这种逻辑体系被称为二值逻辑,而SQL语言里,还有第三个值unknown,因此SQL的逻辑体系被称为三值逻辑。
Why SQL存在三值逻辑?
Because of NULL
理论篇
- 两种NULL、三值逻辑还是四值逻辑
两种NULL:分别指未知(unknown)和不适用(not applicable)。举例:"不知道戴眼镜的人的眼睛是什么颜色",为unknown,"不知道冰箱的眼睛是什么颜色"为not applicable。unknown:虽然现在不知道,但加了条件就可以知道;not applicable:无论怎么努力都不知道。
关系模型的祖师爷Codd博士曾提倡用四值逻辑,所幸的是没人采用。
- 为什么必须写成"IS NULL"而不是"= NULL"
因为对NULL使用谓词后的结果总是unknown,而查询结果只会包含where子句里判断结果为true的行,不会返回包括false和unknown的行。null没有类型,null既不是值也不是变量,只是一个表示"没有值"的标记,而比较谓词只适用于值。
- unknown、第三个真值
真值unknown和作为NULL的一种的UNKNOWN(未知)是不同的东西,前者是明确的布尔型的真值,后者既不是值也不是变量,为了便于区分,unknown为逻辑真值,UNKNOWN为未知。unknown = unknown判断为True,x是UNKNOWN时被判断为unknown。
三值逻辑的优先级:
and:false --> unknown --> true
or:true --> unknown --> false
-- a = 2, b=5,c = NULL
1. a < b AND b > c ==》 true AND unknown ==》 unknown
2. a > b OR b < c ==》 false OR unknown ==》 unknown
3. a < b OR b < c ==》 true OR unknown ==》 true
4. NOT (b <> c) ==》 NOT unknown ==》 unknown
实践篇
比较谓词和NULL(1):排中律不成立
- 生活中:John是20岁 或者 John不是20岁永远成立。“把命题和它的否命题通过"或者"连接而成的命题全都是真命题”这个命题在二值逻辑中被称为排中律(Law of Excluded Middle)。
- SQL里:排中律不成立
name | age |
---|---|
Brown | 22 |
Larry | 19 |
Joan | |
Bird | 21 |
-- 查询年龄是20岁或者不是20岁的学生(生活逻辑则所有人会被找出来)
SELECT * FROM Students WHERE age = 20 OR age <> 20;
-- 1.John的年龄是NULL(unknown)
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;
-- SQL逻辑将会没有John
比较谓词和NULL(2):CASE表达式和NULL
-- col_1为1时返回○,为NULL时返回×的CASE表达式(此写法的结果可能有误)
CASE col_1
WHEN 1 THEN '○'
WHEN NULL THEN '×'
END
-- 简单表达式可能会出现错误,正确的写法如下:
CASE WHEN col_1 = 1 THEN '○'
WHEN col_1 IS NULL THEN '×'
END
NOT IN和NOT EXISTS不是等价的
在对SQL进行性能优化时,经常用到的一个技巧是将IN改写成EXISTS,这是等价改写
Table1:
name | age | city |
---|---|---|
Brown | 22 | 东京 |
Larry | 19 | 埼玉 |
Bird | 21 | 千叶 |
Table2:
name | age | city |
---|---|---|
Kitten | 22 | 东京 |
Tag | 23 | 东京 |
Sandy | 东京 | |
Ming | 18 | 奈良 |
Wutian | 20 | 奈良 |
Lee | 19 | 神奈川 |
-- NOT IN 查询与B班住在东京的同学年龄不同的A班学生的SQL语句(得不到想要的结果)
SELECT * FROM Table1 WHERE age NOT IN (SELECT age FROM Table2 WHERE city = '东京');
-- 1.执行子查询,得到年龄列表
SELECT * FROM Table1 WHERE age NOT IN (22,23,NULL);
-- 2.用NOT 和 IN等价改写NOT IN
SELECT * FROM Table1 WHERE NOT age IN (22,23,NULL);
-- 3.用OR等价改写谓词IN
SELECT * FROM Table1 WHERE NOT ((age = 22) or (age = 23) or (age = NULL));
-- 4.使用德摩根律等价改写
SELECT * FROM Table1 WHERE NOT (age = 22) AND NOT (age = 23) AND NOT (age = NULL);
-- 5.用<>等价改写NOT和=
SELECT * FROM Table1 WHERE age <> 22 AND age <> 23 AND age <> NULL;
-- 6.对NULL使用<>后,结果为unknown
SELECT * FROM Table1 WHERE (age <> 22) AND (age <> 23) AND unknown;
-- 7.如果AND运算里包含unknown,结果不可能为true
SELECT * FROM Table1 WHERE false OR unknown;
-- NOT EXISTS 能给出正确结果
-- 1. 在子查询里和NULL进行比较运算
SELECT * FROM Table1 T1 WHERE NOT EXISTS (SELECT * FROM Table2 T2 WHERE T1.age = NULL AND T2.city = '东京');
-- 2. 对NULL使用"="后,结果为unknown
SELECT * FROM Table1 T1 WHERE NOT EXISTS (SELECT * FROM Table2 T2 WHERE unknown AND T2.city = '东京');
-- 3. 如果AND运算里包含unknown,结果不会是true
SELECT * FROM Table1 T1 WHERE NOT EXISTS (SELECT * FROM Table2 T2 WHERE false OR unknown);
-- 4.子查询没有返回结果,因此相反地,NOT EXISTS为true
SELECT * FROM Table1 T1 WHERE true;
-- 最终A中所有的都被取出来,EXISTS谓词永远不会返回unknown。 因此IN和EXISTS可以互相替代使用,但NOT IN 和NOT EXISTS不能相互替代。
限定谓词与NULL
SQL中有ALL和ANY两个谓词,ANY与IN等价,因此不常用。
-- 查询比B班住在东京的所有学生年龄都小的A班学生(如果山田的年龄为NULL,得不到正确结果)
SELECT * FROM Class_A WHERE age < ALL(SELECT age FROM Class_B WHERE city = '东京');
ALL谓词其实是多个以AND连接的逻辑表达式的省略写法
-- 1.执行子查询获得年龄列表(假设上表山田年龄为NULL)
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使用"<"后,结果变为NULL
SELECT * FROM Class_A WHERE (age < 22) AND (age < 23) AND unknown;
-- 4.如果AND运算符里包含unknown,则结果不为true
SELECT * FROM Class_A WHERE false OR unknown;
限定谓词和极值函数不是等价的
-- 查询比B班住在东京的年龄最小的学生还要小的A班学生(本例侥幸能得到正确结果)
SELECT * FROM Class_A WHERE age < (SELECT MIN(age) FROM Class_B WHERE city = '东京');
-- 极值函数会排除NULL
- ALL谓词:他的年龄比在东京住的而所有学生都小 -Q1
- 极值函数:他的年龄比在东京住的年龄最小的学生还要小 -Q2
- ALL和极值函数不等价的情况:极值函数在输入为空表时会返回NULL
-- 1. 极值函数返回NULL
SELECT * FROM Class_A WHERE age < NULL;
-- 2. 对NULL使用"<"后结果为unknown
SELECT * FROM Class_A WHERE unknown;
聚合函数和NULL
实际上,当输入为空表时返回NULL的还不止极值函数,COUNT以外的聚合函数都是如此
-- 查询比住在东京的学生的平均年龄还要小的A班学生的SQL语句
SELECT * FROM Class_A WHERE age < (SELECT AVG(age) FROM Class_B WHERE city = '东京');
此时若子查询中avg如果为NULL,也得不到任何行。
小结
- NULL不是值,也不是变量,而是一种没有值的标记,没有值的原因分为UNKNOWN和NOT APPLICABLE两种
- 因为NULL不是值,所以不能对其使用谓词
- 对NULL使用谓词的后果是unknown
- unknown参与到逻辑运算时,SQL的运行会和预想的不太一样
- 按步骤追踪SQL的执行过程能有效应对上面的情况。
SQL进阶系列之3三值逻辑与NULL的更多相关文章
- 神奇的 SQL 之温柔的陷阱 → 三值逻辑 与 NULL !
前言 开心一刻 一个中国小孩参加国外的脱口秀节目,因为语言不通,于是找了一个翻译. 主持人问:“Who is your favorite singer ?” 翻译:”你最喜欢哪个歌手啊 ?” 小孩 ...
- SQL进阶系列之7用SQL进行集合运算
写在前面 集合论是SQL语言的根基,因为这种特性,SQL也被称为面向集合语言 导入篇:集合运算的几个注意事项 注意事项1:SQL能操作具有重复行的集合(multiset.bag),可以通过可选项ALL ...
- SQL进阶系列之1CASE表达式
配置环境: 下载地址:https://www.enterprisedb.com/downloads/postgres-postgresql-downloads#windows 使用数据库: C:\Po ...
- Linq To Sql进阶系列(六)用object的动态查询与保存log篇
动态的生成sql语句,根据不同的条件构造不同的where字句,是拼接sql 字符串的好处.而Linq的推出,是为了弥补编程中的 Data != Object 的问题.我们又该如何实现用object的动 ...
- SQL进阶系列之10HAVING子句又回来了
写在前面 HAVING子句的处理对象是集合而不是记录 各队,全队点名 --各队,全体点名! CREATE TABLE Teams (member CHAR(12) NOT NULL PRIMARY K ...
- SQL进阶系列之11让SQL飞起来
写在前面 SQL的性能优化是数据库使用者必须面对的重要问题,本节侧重SQL写法上的优化,SQL的性能同时还受到具体数据库的功能特点影响,这些不在本节讨论范围之内 使用高效的查询 参数是子查询时,使用E ...
- SQL进阶系列之9用SQL处理数列
写在前面 关系模型的数据结构里,并没有顺序的概念,但SQL处理有序集合也有坚实的理论基础 生成连续编号 --生成连续编号 CREATE TABLE Digits (digit INTEGER PRIM ...
- SQL进阶系列之8EXISTS谓词的用法
写在前面 支撑SQL和关系数据库的基础理论:数学领域的集合论和逻辑学标准体系的谓词逻辑 理论篇 什么是谓词?谓词是返回值为真值(true false unknown)的函数 关系数据库里,每一个行数据 ...
- SQL进阶系列之6用关联子查询比较行与行
写在前面 使用SQL对同一行数据进行列间的比较很简单,只需要在WHERE子句里写上比较条件就可以了,对于不同行数据进行列间比较需要使用自关联子查询. 增长.减少.维持现状 需要用到行间比较的经典场景是 ...
随机推荐
- 深入nginx之《获取用户的真实IP》
获取用户的真实IP Nginx会将客户端的IP信息存放在$remote_addr变量里,但这并不意味着它就是客户端的IP,生产环境往往会充满各种代理,让IP的来龙去脉变得扑朔迷离. 目前互联网公司基本 ...
- Ubuntu下载源码并编译
本文章将介绍如何在Ubunt下进行Linux源码下载,并进行简单的编译步骤. 1.下载linux源码 先查看对应的Ubuntu对应版本源码 $ sudo apt-cache search linux- ...
- SQL Server 特殊字符及中文汉字的处理
简介 在SQL Server 中很多时候需要对一些字段中特殊的字符做处理,比如某个字段中包含一些回车.制表.换行等特殊字符(这些字符往往来源于Excel).这些特殊字符的存在可能导致无法提取到所需数据 ...
- 使用 Redis 的 sorted set 实现用户排行榜
要求:实现一个用户排行榜,用户数量有很多,排行榜存储的是用户玩游戏的分数,对排行榜的读取压力比较大,如何实现? 思路分析: 实现排行榜,可以考虑使用 Redis 的 zset 结构: 用户数量很多的话 ...
- php怎么遍历关联和索引数组
foreach $arr = ['a' => 1, 2, 3]; foreach($arr as $key => $value){ // } for $arr = [0, 1, 2, 3] ...
- 精通Spring Boot
原 精通Spring Boot—— 第二十一篇:Spring Social OAuth 登录简介 1.什么是OAuth OAuth官网介绍是这样的: An open protocol to allow ...
- Visual Studio 使用 Parallel Builds Monitor 插件迅速找出编译速度慢的瓶颈,优化编译速度
原文:Visual Studio 使用 Parallel Builds Monitor 插件迅速找出编译速度慢的瓶颈,优化编译速度 嫌项目编译太慢?不一定是 Visual Studio 的问题,有可能 ...
- C#静态字段的两个用处
静态字段的2个常用方法 (1)记录已实例化的对象的个数 (2)存储必须在所有实例化之间共享的值 (1)记录已实例化的对象的个数 现在某个培训机构啊,要开设一个学理发的班,计划招5人,只要人数够5人就开 ...
- 继承与构造函数(base关键字)
1.背景 我:虽然通过继承减少了代码冗余,但是,每一个子类的构造函数还是需要给所有属性赋值的,很麻烦的. 师:这个好办,用base就行啦. 我:贝司?还吉他呢! 师:别急,首先我们先介绍下实例化子类对 ...
- Spring AOP 创建Advice 定义pointcut、advisor
前面定义的advice都是直接植入到代理接口的执行之前和之后,或者在异常发生时,事实上,还可以对植入的时机定义的更细. Pointcut定义了advice的应用时机,在Spring中pointcutA ...