写在前面

普通编程语言里的布尔型只有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的更多相关文章

  1. 神奇的 SQL 之温柔的陷阱 → 三值逻辑 与 NULL !

    前言 开心一刻   一个中国小孩参加国外的脱口秀节目,因为语言不通,于是找了一个翻译. 主持人问:“Who is your favorite singer ?” 翻译:”你最喜欢哪个歌手啊 ?” 小孩 ...

  2. SQL进阶系列之7用SQL进行集合运算

    写在前面 集合论是SQL语言的根基,因为这种特性,SQL也被称为面向集合语言 导入篇:集合运算的几个注意事项 注意事项1:SQL能操作具有重复行的集合(multiset.bag),可以通过可选项ALL ...

  3. SQL进阶系列之1CASE表达式

    配置环境: 下载地址:https://www.enterprisedb.com/downloads/postgres-postgresql-downloads#windows 使用数据库: C:\Po ...

  4. Linq To Sql进阶系列(六)用object的动态查询与保存log篇

    动态的生成sql语句,根据不同的条件构造不同的where字句,是拼接sql 字符串的好处.而Linq的推出,是为了弥补编程中的 Data != Object 的问题.我们又该如何实现用object的动 ...

  5. SQL进阶系列之10HAVING子句又回来了

    写在前面 HAVING子句的处理对象是集合而不是记录 各队,全队点名 --各队,全体点名! CREATE TABLE Teams (member CHAR(12) NOT NULL PRIMARY K ...

  6. SQL进阶系列之11让SQL飞起来

    写在前面 SQL的性能优化是数据库使用者必须面对的重要问题,本节侧重SQL写法上的优化,SQL的性能同时还受到具体数据库的功能特点影响,这些不在本节讨论范围之内 使用高效的查询 参数是子查询时,使用E ...

  7. SQL进阶系列之9用SQL处理数列

    写在前面 关系模型的数据结构里,并没有顺序的概念,但SQL处理有序集合也有坚实的理论基础 生成连续编号 --生成连续编号 CREATE TABLE Digits (digit INTEGER PRIM ...

  8. SQL进阶系列之8EXISTS谓词的用法

    写在前面 支撑SQL和关系数据库的基础理论:数学领域的集合论和逻辑学标准体系的谓词逻辑 理论篇 什么是谓词?谓词是返回值为真值(true false unknown)的函数 关系数据库里,每一个行数据 ...

  9. SQL进阶系列之6用关联子查询比较行与行

    写在前面 使用SQL对同一行数据进行列间的比较很简单,只需要在WHERE子句里写上比较条件就可以了,对于不同行数据进行列间比较需要使用自关联子查询. 增长.减少.维持现状 需要用到行间比较的经典场景是 ...

随机推荐

  1. node读写本地文件

    http://nodejs.cn/api/fs.html#fs_fs_writefilesync_file_data_options https://www.cnblogs.com/diaosizha ...

  2. JEECG实现模糊查询

    1.JEECG默认不带模糊查询的,但实际开发中会经常用到模糊查询,因此要适当修改(在相应的查询处). @RequestMapping(params = "datagrid") pu ...

  3. CentOS7.5 使用二进制程序部署Kubernetes1.12.2(三)

    一.安装方式介绍 1.yum 安装 目前CentOS官方已经把Kubernetes源放入到自己的默认 extras 仓库里面,使用 yum 安装,好处是简单,坏处也很明显,需要官方更新 yum 源才能 ...

  4. 解决org.springframework.dao.DeadlockLoserDataAccessException

    添加链接池后批量添加更新出现了死锁 org.springframework.dao.DeadlockLoserDataAccessException: ### Error updating datab ...

  5. Sublime Text3 搭建前端开发环境

    第一步:百度搜索sublime text3 ,直接点击红色箭头下方的下载地址,下载完成安装后会提示是否更新,直接点击更新就好了! 第二步:下载插件管理器,点击菜单栏Tools->Package ...

  6. ACM- 编程练习网站--输入数据方法

    #include "stdafx.h" #include <iostream> #include <string> #include <algorit ...

  7. appium学习笔记(环境安装配置,设备启动)

      Android SDK下载及配置 下载地址 下载后解压缩,打开SDK Manager.exe,下载适当版本的Android包 配置环境变量:目录下的tools路径(含uiautomatorview ...

  8. 006 SpringCloud 学习笔记2-----SpringCloud基础入门

    1.SpringCloud概述 微服务是一种架构方式,最终肯定需要技术架构去实施. 微服务的实现方式很多,但是最火的莫过于Spring Cloud了.SpringCloud优点: - 后台硬:作为Sp ...

  9. Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activate 'xxxxx.Controllers.xxxxController'.

    Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activa ...

  10. aspnetcore identity result.Succeeded SignInManager.IsSignedIn(User) false?

    登陆返回的是 result.Succeeded 为什么跳转到其他页面SignInManager.IsSignedIn(User)为false呢? result.Succeeded _signInMan ...