1-8 EXISTS谓词的用法

谓词的阶数

=或者BETWEEN等输入值为一行的谓词叫做“一阶谓词”,像EXISTS这样输入值为行的集合的谓词叫做“二阶谓词”

全称量化和存在量化

全称量词:所有的x都满足条件P,记作∀(字母A上下颠倒。for All x,……)

存在量词:存在(至少一个)满足条件P的x,记作∃(字母E左右颠倒,there Exists x that……)

SQL 中的EXISTS谓词实现了谓词逻辑中的存在量词,但SQL并没有实现全称量词。

可以根据德·摩根定律用存在量词推导全称量词:

∀xPx = ¬∃x¬P 所有的x都满足条件P = 不存在不满足条件P的x

∃xPx = ¬∀x¬Px 存在x满足条件P = 并非所有的x都不满足条件P

查询表中不存在的数据

要找出没有参加某次会议的人。

思路:先假设所有人都参加了全部会议,并以此生成一个集合,然后从中减去实际参加会议的人,就能得到缺席会议的人。

-- 求出缺席者的SQL语句(1):存在量化的应用
SELECT DISTINCT M1.meeting, M2.person
FROM Meetings M1 CROSS JOIN Meetings M2
WHERE NOT EXISTS(SELECT *FROM Meetings M3WHERE M1.meeting = M3.meetingAND M2.person = M3.person);

这道题还可以用集合论的方法来解答,即像下面这样使用差集运算:

-- 求出缺席者的SQL语句(2):使用差集运算
SELECT M1.meeting, M2.person
FROM Meetings M1, Meetings M2
EXCEPT
SELECT meeting, person
FROM Meetings;

全称量化(1):习惯“肯定⇔双重否定”之间的转换

有一张存储了学生考试成绩的表,请查询出“所有科目分数都在50分以上的学生”。

思路:将查询条件“所有科目分数都在50分以上”转换成它的双重否定“没有一个科目分数不满50分”,然后用NOT EXISTS表示

SELECT DISTINCT student_id
FROM TestScores TS1
WHERE NOT EXISTS(SELECT *FROM TestScores TS2WHERE TS2.student_id = TS1.student_idAND TS2.score < 50);

请查询出数学分数在80分以上且语文分数在50分以上的学生。学号400的学生没有语文分数的数据,但是也需要包含在结果里。

SELECT DISTINCT student_id
FROM TestScores TS1
WHERE subject in ('数学', '语文')
AND NOT EXISTS(SELECT *FROM TestScores TS2WHERE TS2.student_id = TS1.student_id
AND 1 = CASE WHEN subject='数学' AND score<80 THEN 1WHEN subject='语文' AND score<50 THEN 1ELSE 0 END);

如果要排除掉没有语文分数的学号为400的学生,可以加上用于判断行数的HAVING子句来实现

SELECT student_id
FROM TestScores TS1
WHERE subject IN ('数学', '语文')
AND NOT EXISTS(SELECT *FROM TestScores TS2WHERE TS2.student_id = TS1.student_id
AND 1 = CASE WHEN subject='数学' AND score<80 THEN 1WHEN subject='语文' AND score<50 THEN 1ELSE 0 END)
GROUP BY student_id
HAVING COUNT(*) = 2;        -- 必须两门科目都有分数

全称量化(2):集合VS谓词——哪个更强大?

假设存在上面这样的项目工程管理表。请从这张表中查询出哪些项目已经完成到了工程1。

-- 面向集合的解法
SELECT project_id
FROM Projects
GROUP BY project_id
HAVING COUNT(*) = SUM(CASE WHEN step_nbr <= 1AND status = '完成' THEN 1WHEN step_nbr > 1AND status = '等待' THEN 1ELSE 0 END);
-- 谓词逻辑的解法
SELECT *
FROM Projects P1
WHERE NOT EXISTS(SELECT statusFROM Projects P2WHERE P1.project_id = P2.project_idAND status <> CASE WHEN step_nbr <= 1THEN '完成'ELSE '等待' END);

NOT EXISTS缺点:与HAVING相比,使用双重否定的NOT EXISTS代码并不容易理解;

NOT EXISTS优点:性能好,只要有一行满足条件,查询就会终止,而且通过连接条件使用“project_id“列的索引,查询起来更快;结果里能包含的信息量更大,如果用HAVING,结果会被聚合,只能获得项目ID,如果使用EXISTS,能把集合里的元素整体获取到。

对列进行量化:查询全是1的行

-- 列方向的全称量化:查询全是1的行
SELECT *
FROM ArrayTbl
WHERE 1 = ALL(col1, col2, col3, col4, col5, col6, col7, col8, col9, col10);
-- 列方向的存在量化:查询至少有一个9的行
SELECT *
FROM ArrayTbl
WHERE 9 = ANY(col1, col2, col3, col4, col5, col6, col7, col8, col9, col10);

可以用IN谓词代替ANY

SELECT *
FROM ArrayTbl
WHERE 9 IN (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10);
-- 查询全是NULL的行
SELECT *
FROM ArrayTbl
WHERE COALESCE(col1, col2, col3, col4, col5, col6, col7, col8, col9, col10) IS NULL;

练习题1-8-1:数组表——行结构表的情况

i列表示数组的下标,主键为“key, i“。key为A的行val全部是NULL,key为B的行只有i=1的行val是3,其他的都为NULL,key为C的行val全部是1。请从这张表中选出val全为1的key。

--利用NOT EXISTS
SELECT DISTINCT key
FROM ArrayTbl2 A1
WHERE NOT EXISTS(SELECT *FROM ArrayTbl2 A2WHERE A1.key = A2.keyAND (A2.val <> 1 OR A2.val IS NULL));

如果不加上A2.val IS NULL,A也会出现在结果中。因为A的val字段全为NULL,val<>1的结果是unknown,而EXISTS谓词是二值逻辑,NOT EXISTS会把A的记录当成TRUE。

还可以使用ALL谓词或者HAVING子句:

-- 使用ALL谓词
SELECT DISTINCT key
FROM ArrayTbl2 A1
WHERE 1 = ALL(SELECT val FROM ArrayTbl2 A2WHERE A1.key = A2.key);
-- 使用HAVING子句
SELECT key
FROM ArrayTbl2
GROUP BY key
HAVING MAX(val) = 1AND MIN(val) = 1;

练习题1-8-2:使用ALL谓词表达全称量化

请用ALL谓词改写”全称量化(2):集合VS谓词——哪个更强大?“部分的SQL语句。

SELECT *
FROM Projects P1
WHERE 'o' = ALL
(SELECT CASE WHEN step_nbr <= 1 AND status = '完成'               THEN 'o'WHEN step_nbr > 1 AND status = '等待'              THEN 'o'ELSE 'x' ENDFROM Projects P2WHERE P1.project_id = P2.project_id);

练习题1-8-3:求质数

Numbers表中存储了1~100的所有自然数,列名为num。求出Numbers表中的所有质数(除了1和它自身之外不能被任何自然数整除且大于1的自然数)。

SELECT num AS prime
FROM Numbers Dividend
WHERE num > 1AND NOT EXISTS(SELECT *FROM Numbers DivisorWHERE Divisor.num <= Dividend.num / 2AND Divisor.num <> 1AND MOD(Dividend.num, Divisor.num) = 0)
ORDER BY prime;

准备被除数(Dividend)和除数(Divisor)的集合。因为约数不包含自身,所以约数一定小于等于自身值的一半。

1-9 用SQL处理数列

生成连续编号

无论多大的数,都是由0~9这10个数字组成的。首先生成一张存储了这10个数字的表Digits,列名为digit。 通过对两个Digits集合求笛卡尔积得出0~99的数字。

SELECT D1.digit + (D2.digit * 10) AS seq
FROM Digits D1 CROSS JOIN Digits D2
ORDER BY seq;

通过生成序列的视图,就可以在需要连续编号的时候通过简单的SELECT来获取需要的编号。

-- 生成序列视图(包含0~999)
CREATE VIEW Sequence(seq)
AS SELECT D1.digit+(D2.digit * 10)+(D3.digit * 100)FROM Digits D1 CROSS JOIN Digits D2CROSS JOIN Digits D3;
-- 从序列视图中获取1~100
SELECT seq
FROM Sequence
WHERE seq BETWEEN 1 AND 100
ORDER BY seq;

三个人能坐得下吗

上表存储了火车座位的预定情况。假设3个人去旅行,准备预定这列火车的车票。要从1~15的座位编号中找出连续3个空位的全部组合。假设所有的座位排成一条直线。

SELECT S1.seat AS start_seat, '~', S2.seat AS end_seat
FROM Seats S1, Seats S2
WHERE S2.seat = S1.seat + (:head_cnt - 1)AND NOT EXISTS(SELECT *FROM Seats S3WHERE S3.seat BETWEEN S1.seat AND S2.seatAND S3.status <> '未预订');

:head_cnt是表示需要的空位个数的参数。 S2.seat = S1.seat + (:head_cnt - 1)生成起点和终点的组合,保证结果中只出现包含需要空位数的序列。 S3.seat BETWEEN S1.seat AND S2.seat AND S3.status <> '未预订’描述了起点到终点之间所有点需要满足的条件“所有座位的状态都是未预订”,用NOT EXISTS改写。

考虑换排的情况

因为换排,所以9~11的序列不符合条件。

需要加上“全部座位都在一排”这个条件。

SELECT S1.seat AS start_seat, '~', S2.seat AS end_seat
FROM Seats S1, Seats S2
WHERE S2.seat = S1.seat + (:head_cnt - 1)AND NOT EXISTS(SELECT *FROM Seats S3WHERE S3.seat BETWEEN S1.seat AND S2.seatAND (S3.status <> '未预订'OR S3.row_id <> S1.row_id));

最多能坐下多少人

要查询“按现在的空位状况,最多能坐下多少人”。

思路:这样的序列需要满足三个条件:

  1. 起点到终点之间的所有座位状态都是“未预订”
  2. 起点之前的座位状态不是“未预订”
  3. 终点之后的座位状态不是“未预订”

先生成存储所有这样的序列的视图,然后找出长度最大的序列:

-- 第一阶段:生成存储了所有序列的视图
CREATE VIEW Sequences(start_seat,end_seat,seat_cnt)
AS
SELECT S1.seat AS start_seat,S2.seat AS end_seat,S2.seat - S1.seat + 1 AS seat_cnt
FROM Seats3 S1, Seats3 S2
WHERE S1.seat <= S2.seatAND NOT EXISTS(SELECT *FROM Seats3 S3WHERE (S3.seat BETWEEN S1.seat AND S2.seatAND S3.status <> '未预订')-- 条件1的否定OR (S3.seat = S2.seat + 1 AND S3.status = '未预订')-- 条件2的否定OR (S3.seat = S1.seat - 1 AND S3.status = '未预订')  -- 条件3的否定
-- 第二阶段:求最长的序列
SELECT start_seat, '~', end_seat, seat_cnt
FROM Sequences
WHERE seat_cnt = (SELECT MAX(seat_cnt) FROM Sequences);

单调递增和单调递减

上表反映了某公司的股价动态。要求股价单调递增的时间区间。

SELECT S1.deal_date AS start_date,S2.deal_date AS end_date
FROM MyStock S1, MyStock S2
WHERE S1.deal_date < S2.deal_dateAND NOT EXISTS(SELECT *FROM MyStock S3, MyStock S4WHERE S3.deal_date BETWEEN S1.deal_date AND S2.deal_dateAND S4.deal_date BETWEEN S1.deal_date AND S2.deal_dateAND S3.deal_date < S4.deal_dateAND S3.price >= S4.price);

执行结果:

start_date  end_date
2007-01-06  2007-01-08
2007-01-14  2007-01-16
2007-01-14  2007-01-17
2007-01-16  2007-01-17

结果中包含最长时间区间的子集。用极值函数将其去掉。

SELECT MIN(start_date) AS start_date, end_date
FROM (
SELECT S1.deal_date AS start_date,S2.deal_date AS end_date
FROM MyStock S1, MyStock S2
WHERE S1.deal_date < S2.deal_dateAND NOT EXISTS(SELECT *FROM MyStock S3, MyStock S4WHERE S3.deal_date BETWEEN S1.deal_date AND S2.deal_dateAND S4.deal_date BETWEEN S1.deal_date AND S2.deal_dateAND S3.deal_date < S4.deal_dateAND S3.price >= S4.price)GROUP BY start_date) TMP
GROUP BY end_date;

练习题1-9-2:求序列——面向集合的思想

在“三个人能坐得下吗”部分,我们用NOT EXISTS表达了全称量化,进而求出了序列,请思考如何使用HAVING子句来解决这个问题。

思路:将双重否定条件改为肯定条件。

-- 不考虑换排情况
SELECT S1.seat AS start_seat, S2.seat AS end_seat
FROM Seats S1, Seats S2, Seats S3
WHERE S2.seat = S1.seat + (:head_cnt - 1)AND S3.seat BETWEEN S1.seat AND S2.seat
GROUP BY S1.seat, S2.seat
HAVING count(*) = SUM(CASE WHEN S3.status = '未预订' THEN 1 ELSE 0 END);
-- 考虑换排情况
SELECT S1.seat AS start_seat, S2.seat AS end_seat
FROM Seats2 S1, Seats2 S2, Seats2 S3
WHERE S2.seat = S1.seat + (:head_cnt - 1)AND S3.seat BETWEEN S1.seat AND S2.seat
GROUP BY S1.seat, S2.seat
HAVING count(*) = SUM(CASE WHEN S3.status = '未预订' AND S3.row_id = S1.row_id THEN 1 ELSE 0 END);

练习题1-9-3:求所有的序列——面向集合的思想

在“最多能坐下多少人”部分,我们使用NOT EXISTS求出了所有序列,并将它们存储在视图里。请将该SQL语句用HAVING子句改写。

CREATE VIEW Sequences(start_seat,end_seat,seat_cnt)
AS
SELECT S1.seat AS start_seat,S2.seat AS end_seat,S2.seat - S1.seat + 1 AS seat_cnt
FROM Seats3 S1, Seats3 S2, Seats3 S3
WHERE S1.seat <= S2.seat
AND S3.seat BETWEEN S1.seat - 1 AND S2.seat + 1
GROUP BY S1.seat, S2.seat
HAVING COUNT(*) = SUM(CASE WHEN S3.seat BETWEEN S1.seat AND S2.seatAND S3.status = '未预订' THEN 1 ELSE 0WHENS3.seat = S2.seat + 1 AND S3.status <> '未预订' THEN 1 ELSE 0WHENS3.seat = S1.seat - 1 AND S3.status <> '未预订' THEN 1 ELSE 0END);

1-10 HAVING子句又回来了

各队,全体点名

从上表中查出所有队员都处在“待命”状态的队伍。

-- 用NOT EXISTS表达全称量化命题
SELECT team_id, member
FROM Teams T1
WHERE NOT EXISTS(SELECT *FROM Teams T2WHERE T1.team_id = T2.team_idAND status <> '待命');
-- 用集合表达全称量化命题(1)
SELECT team_id
FROM Teams
GROUP BY team_id
HAVING COUNT(*) = SUM(CASE WHEN status = '待命'   THEN 1 ELSE 0 END);
-- 处于“待命”状态的数据行数与集合中数据总行数相等
-- 用集合表达全称量化命题(2)
SELECT team_id
FROM Teams
GROUP BY team_id
HAVING MAX(status) = '待命'AND MIN(status) = '待命';

也可以将条件放在SELECT子句中,以列表形式显示出各个队伍是否所有队员都在待命。

SELECT team_idCASE WHEN MAX(status) = '待命'AND MIN(status) = '待命'THEN '全都在待命'ELSE '队长!人手不够' END AS status
FROM Teams
GROUP BY team_id;

单重集合与多重集合

Materials是一张管理各个生产地的材料库存的表。现在需要调查出存在重复材料的生产地。

SELECT center
FROM Materials
GROUP BY center
HAVING COUNT(material) <> COUNT(DISTINCT material);

也可以通过将HAVING改写为EXISTS的方式来解决。

SELECT center, material
FROM Materials M1
WHERE EXISTS(SELECT *FROM Materials M2WHERE M1.center = M2.centerAND M1.receive_date <> M2.receive_dateAND M1.material = M2.material);

寻找缺失的编号:升级版

1-4节中介绍了下面这种查询数列缺失编号的查询语句:

SELECT '存在缺失的编号' AS gap
FROM SeqTbl
HAVING COUNT(*) <> MAX(seq);

这条SQL语句的前提是数列的起始值为1。但一个数列是否存在缺失编号一共有四种情况:

  1. 不存在缺失编号(起始值=1),1,2,3,4,5
  2. 存在缺失编号(起始值=1),1,2,4,5,8
  3. 不存在缺失编号(起始值<>1),3,4,5,6,7
  4. 存在缺失编号(起始值<>1),3,4,7,8,10

将SQL语句改进的能适合更多情况:

SELECT CASE WHEN COUNT(*) = 0THEN '表为空'WHEN COUNT(*) <> MAX(seq) - MIN(seq) + 1THEN '存在缺失的编号'ELSE '连续' END AS gap
FROM SeqTbl;

查找最小的缺失编号:

SELECT CASE WHEN COUNT(*) = 0 OR MIN(seq) > 1THEN 1        -- 最小值不为1是返回1ELSE (SELECT MIN(seq + 1)FROM SeqTbl S1WHERE NOT EXISTS(SELECT *FROM SeqTbl S2WHERE S2.seq = S1.seq + 1))-- 最小值为1时返回最小的缺失值END
FROM SeqTbl;

为集合设置详细的条件

这张表记录了学生的基本信息和考试成绩。 请查询出75%以上的学生分数都在80分及以上的班级。

SELECT class
FROM TestResults
GROUP BY class
HAVING COUNT(*) * 0.75<= SUM(CASE WHEN score >= 80THEN 1 ELSE 0 END);

请查询出分数在50分及以上的男生人数比分数在50分及以上的女生人数多的班级。

SELECT class
FROM TestResults
GROUP BY class
HAVING SUM(CASE WHEN score >= 50 AND sex = '男'THEN 1 ELSE 0 END)> SUM(CASE WHEN score >= 50 AND sex = '女'THEN 1 ELSE 0 END);

练习题1-10-2:多个条件的特征函数

1-8节中我们使用谓词逻辑的方法查询了满足“数学分数在80分及以上且语文分数在50分及以上的学生”。请试着用HAVING子句和特征函数的方法解答一下。

SELECT student_id
FROM TestScores
WHERE subject IN ('数学', '语文')
GROUP BY student_id
HAVING SUM(CASE WHEN subject = '数学' AND score >= 80 THEN 1WHEN subject = '语文' AND score >= 50 THEN 1ELSE 0 END) = 2;

作者在本书的后记中讲述了自己从刚开始接触关系型数据库时第一印象并不好到写出SQL教程这之间的转变原因,他是在读了一本讲述UNIX系统管理员心得的书籍之后开始重新审视数据库的。他在读完那本书之后得到的启发是:在这个世界上,无论看起来多么普通的事物,背后总是隐藏着深刻的原理。那本书的评论中还有这样一句话:”这个世界没有一天是无聊的,只不过是你懒惰已久的感官无法发现眼前事物的乐趣而已。“

希望大家都能发现生活中的乐趣,成为一个有趣的人:)

exists sql用法_《SQL进阶教程》笔记(3)相关推荐

  1. exists sql用法_干货!SQL性能优化,书写高质量SQL语句

    写SQL语句的时候我们往往关注的是SQL的执行结果,但是是否真的关注了SQL的执行效率,是否注意了SQL的写法规范? 以下的干货分享是在实际开发过程中总结的,希望对大家有所帮助! 1. limit分页 ...

  2. exists sql用法_彻底弄懂sql select各种查询用法

    相信很多人和我一样,学习sql 就是记忆各种sql的语法,但是记了一大堆的语法,在遇到实际查询问题时又无从下手的感觉.本文主要是针对sql 中select用法的总结,用于帮助大家解决记了相关语法却不知 ...

  3. 读SQL进阶教程笔记14_SQL编程要点

    1. 消灭NULL 1.1. NULL惹人讨厌的原因 1.1.1. 进行SQL编码时,必须考虑违反人类直觉的三值逻辑 1.1.2. 指定IS NULL.IS NOT NULL的时候,不会用到索引,SQ ...

  4. 读SQL进阶教程笔记12_地址与三值逻辑

    1. SQL和数据库都在极力提升数据在表现层的抽象度,以及对用户隐藏物理层的概念 2. 关系模型是为摆脱地址而生的 2.1. "地址"不仅包括指针操作的地址,还包括数组下标等 3. ...

  5. distinct sql用法_十分钟搞懂SQL数据分析

    风控说 由上海新金融风险实验室出品

  6. promise用法_【JavaScript 教程】异步操作——Promise 对象

    作者 | 阮一峰 概述 Promise 对象是 JavaScript 的异步操作解决方案,为异步操作提供统一接口.它起到代理作用(proxy),充当异步操作与回调函数之间的中介,使得异步操作具备同步操 ...

  7. oracle concat函数用法_大白的函数笔记:文本合并就是这么简单,不会的快来学...

    我们在工作中,经常会遇到需要把几个单元格的内容连接起来的情况,针对这种情况Excel为我们提供了几种方法:CONCATENATE函数.CONCAT函数"&"连接符和PHON ...

  8. access month函数用法_小白进阶必备的10组函数公式实用技巧解读,有案例和详情解读哦!...

    函数公式可以说是Excel的灵魂,所以对于一些基础实用性的函数公式我们必须掌握哦! 一.Len.Lenb:返回文本字符串中的字符数个数或字符数. 目的:返回指定字符串的字数和字节数. 方法: 在目标单 ...

  9. 组态王bitset用法_组态王教程(基础入门篇).pdf

    组态王教程及使用问题解答(基础篇) 一.组态王教程 简介:本教程是学习"组态王"软件的入门教程,覆盖了"组态王"软件的大部分基本功能.学完本教程后, 您将能够建 ...

  10. python 123 io网站答题如果最小化会有提示吗_爬虫进阶教程:百万英雄答题辅助系统...

    百万英雄答题辅助系统 var socket = io.connect('http://你的IP:端口'); var line1 = document.getElementById('line1'); ...

最新文章

  1. 查看mongodb数据路径_【数据库】mongodb数据库安装
  2. 激光雷达和相机感知融合简介
  3. 职场经典小故事-II
  4. 我为什么更喜欢 Mac OS X
  5. 如何招聘一个优秀的产品经理?Google主管的六条心得
  6. 关于win7禁止标准用户安装软件 AppLocker使用
  7. Android 中的Json解析工具fastjson 、序列化、反序列化
  8. CSS clip:rect矩形剪裁功能
  9. .net Core 生产环境 KestrelServer + Shell 实践
  10. 计算机应用问题,计算机应用的现状与发展的问题
  11. C++ 11 深度学习(四)结构、权限修饰符
  12. MaxCompute_2_MaxCompute数据迁移文档
  13. netty时间轮HashedWheelTimer文档翻译及简单说明
  14. 转载——python字符串常用操作(加案例)
  15. unicode官网 unicode码表和标准下载
  16. psftp 上传和下载
  17. 在 MATLAB 或 Python 中使用 ZOS-API 进行光线追跡的批次处理
  18. 硬盘安装Win7教程!无光驱无U盘照样装Win7
  19. html一像素等于多少px,pt和px换算(一pt等于多少像素)
  20. 免费公益云服务器主机推荐

热门文章

  1. 15-struct(构造函数,重载)
  2. java基础学习总结——流
  3. 自定义UITabBar的两种方式
  4. nim3取石子游戏 (威佐夫博弈)
  5. 引擎设计跟踪(九.2) 3DS MAX 导出插件 继续
  6. 华为/华三IS-IS单区域配置
  7. avc水平什么意思_5个步骤切实有效地提高你的写作水平
  8. java接口之双端队列
  9. Python处理多种编码报错的处理
  10. 马斯克:特斯拉将发布结合太阳能、电池存储技术的新产品