你真的了解SQL中的EXISTS谓词吗?

news/2024/5/5 21:14:30/文章来源:https://blog.csdn.net/Huangjiazhen711/article/details/127221450

EXISTS 谓词的用法

支撑 SQL 和关系数据库的基础理论主要有两个:一个是数学领域的集合论,另一个是作为现代逻辑学标准体系的谓词逻辑(predicate logic),准确地说是“一阶谓词逻辑”。EXISTS 是为了实现谓词逻辑中“量化”(quantification)这一强大功能而被引入 SQL 的。

1. 什么是谓词?

1.1 概述

SQL 的保留字中,有很多都被归为谓词一类。例如,“= 、< 、>”等比较谓词,以及 BETWEEN 、LIKE 、IN 、IS NULL 等。当然,我们说的谓词和主语/谓语中的谓语,以及英语中的动词是不一样的。

实际上,谓词是一种特殊的函数,返回值是真值。前面提到的每个谓词,返回值都是 true 、 false 或者 unknown (一般的谓词逻辑里没有 unknown ,但是 SQL 采用的是三值逻辑,因此具有三种真值)。谓词逻辑提供谓词是为了判断命题(可以理解成陈述句)的真假,而在关系数据库里,表中的一行数据可以看作是一个命题。

Tbl_A:

例如,这张表里第一行数据就可以认为表示这样一个命题:田中性别是男,而且年龄是 28 岁。

表常常被认为是行的集合,但从谓词逻辑的观点看,也可以认为是命题的集合(也就是描述每一行记录陈述句的集合)。

也就是说,我们平时使用的 WHERE 子句,其实也可以看成是由多个谓词组合而成的新谓词。只有能让 WHERE 子句的返回值为真的命题,才能从表(命题的集合)中查询到。

1.2 实体的阶层

同样是谓词,但是与 = 、BETWEEN 等相比,EXISTS 的用法还是大不相同的。概括来说,区别在于“谓词的参数可以取什么值”。“x = y ”或“x BETWEEN y ”等谓词可以取的参数是像“13”或者“本田”这样的单一值,我们称之为标量值。而 EXISTS 可以取的参数究竟是什么呢?从下面这条 SQL 语句来看,EXISTS 的参数不像是单一值。

SELECT id
FROM Foo F
WHERE EXISTS (SELECT * FROM Bar B WHERE F.id=B.id );

可以看出参数是行数据的集合。之所以这么说,是因为无论子查询中选择什么样的列,对于 EXISTS 来说都是一样的。

从上面的图表我们可以知道,EXISTS 的特殊性在于输入值的阶数(输出值和其他谓词一样,都是真值)。谓词逻辑中,根据输入值的阶数对谓词进行分类。= 或者 BETWEEEN 等输入值为一行的谓词叫作“一阶谓词”,而像 EXISTS 这样输入值为行的集合的谓词叫作“二阶谓词”。阶(order)是用来区分集合或谓词的阶数的概念。

类似Java中的“高阶函数”这一概念。它指的是不以一般的原子性的值为参数,而以函数为参数的函数。这里说的“阶”和谓词逻辑里的“阶”是一个意思(“阶”的概念原本就源于集合论和谓词逻辑)。EXISTS 因接受的参数是集合这样的一阶实体而被称为二阶谓词,但是谓词也是函数的一种,因此我们也可以说 EXISTS 是高阶函数。

SQL 中采用的是狭义的“一阶谓词逻辑”,这是因为 SQL 里的 EXISTS 谓词最高只能接受一阶的实体作为参数。如果想要支持二阶、三阶等更高阶的实体,SQL 必须提供相应的支持。理论上这也是可以做到的 ,只是目前还没有实现。

1.3 全称量化和存在量化

谓词逻辑中有量词(限量词、数量词)这类特殊的谓词。我们可以用它们来表达一些这样的命题:“所有的 x 都满足条件 P”或者“存在(至少一个)满足条件 P 的 x ”。前者称为“全称量词”,后者称为“存在量词”,分别记作 ∀、∃。

也许大家已经明白了,SQL 中的 EXISTS 谓词实现了谓词逻辑中的存在量词。然而遗憾的是,对于另一个全称量词,SQL 却并没有予以实现。

但是没有全称量词并不算是 SQL 的致命缺陷。因为全称量词和存在量词只要定义了一个,另一个就可以被推导出来。具体可以参考这个等价改写的规则(德·摩根定律):

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

2. 查询表中“不”存在的数据

2.1 小试牛刀

Meetings:

现在需要查询的是没有参加某次会议的人,下面给出了议缺席者列表:

meeting    person
---------- --------
第 1 次     宫田
第 2 次     坂东
第 2 次     水岛
第 3 次     伊藤

首先可以想到查询出所有人都参加了所有会议的情况:

SELECT DISTINCT M1.meeting, M2.person
FROM Meetings M1 INNER JOIN Meetings M2;

然后我们从这张表中减掉实际参会者的集合,即表 Meetings 中存在的组合即可。

-- 求出缺席者的SQL 语句(1):存在量化的应用
SELECT DISTINCTM1.meeting,M2.person 
FROMMeetings M1INNER JOIN Meetings M2 
WHERENOT EXISTS ( SELECT * FROM Meetings M3 WHERE M1.meeting = M3.meeting AND M2.person = M3.person );
---- 求出缺席者的 SQL 语句(2):使用差集运算 
SELECT
M1.meeting,
M2.person 
FROMMeetings M1, Meetings M2 
EXCEPT
SELECTmeeting,person 
FROMMeetings;

通过以上两条 SQL 语句的比较我们可以明白,NOT EXISTS 直接具备了差集运算的功能。

2.2 “肯定与双重否定”之间的转换

TestScores:

上图是一张存储了学生考试成绩的表,这里只展示出了部分数据。

先来看一个简单的问题:请查询出“所有科目分数都在 50 分以上的学生”,解法是,将查询条件“所有科目分数都在 50 分以上”转换成它的双重 否定“没有一个科目分数不满 50 分”,然后用 NOT EXISTS 来表示转换后的命题。

SELECT DISTINCTstudent_id 
FROMTestScores TS1 
WHERENOT EXISTS ( SELECT * FROM TestScores TS2 WHERE TS2.student_id = TS1.student_id AND TS2.score < 50 );

现在需要查询出满足“该学生的所有行数据中,数学分数在 80 分以上;语文分数在 50 分以上”的学生。

SELECTstudent_id 
FROMTestScores TS1 
WHERESUBJECT IN ( '数学', '语文' ) AND NOT EXISTS (SELECT* FROMTestScores TS2 WHERETS2.student_id = TS1.student_id AND 1 =CASE WHEN SUBJECT = '数学' AND score < 80 THEN 1 WHEN SUBJECT = '语文' AND score < 50 THEN 1 ELSE 0 END );
GROUP BY student_id
HAVING COUNT(*) = 2; 

首先,数学和语文之外的科目不在我们考虑范围之内,所以通过 IN 条件进行一下过滤。然后,通过子查询来描述“数学 80 分以上,语文 50 分以上”这个条件。再者,学生必须两门科目都有分数才行,所以我们可以加上用于判断行数的 HAVING 子句来实现。

2.3 “集合与谓词”哪个更强大?

EXISTS 和 HAVING 有一个地方很像,即都是以集合而不是个体为单位来操作数据。实际上,两者在很多情况下都是可以互换的,用其中一个写出的查询语句,大多时候也可以用另一个来写。

Projects:

这张表的主键是“项目 ID,工程编号”。工程编号从 0 开始,我们不妨认为 0 号是需求分析,1 号是基本设计……虽然这张表中部分数据的工程编号最大只到 2,但是有可能也会有 3 及以后的编号。已经完成的工程其状态列的是“完成”,等待上一个工程完成的工程其状态列的是“等待”。

这里的问题是,从这张表中查询出哪些项目已经完成到了 (也就是说1及之前的工程都处于完成状态,1之后的工程都处于等待状态)工程 1。

-- 查询完成到了工程1 的项目:面向集合的解法
SELECTproject_id 
FROMProjects 
GROUP BYproject_id 
HAVINGCOUNT(*) = SUM( CASE WHEN step_nbr <= 1 AND STATUS = '完成' THEN 1 WHEN step_nbr > 1 AND STATUS = '等待' THEN 1 ELSE 0 END);

这里针对每个项目,将工程编号为1以下且状态为“完成”的行数,和工程编号大于1且状态为“等待”的行数加在一起,如果和等于该项目数据的总行数,则该项目符合查询条件。

-- 查询完成到了工程1 的项目:谓词逻辑的解法
SELECT* 
FROMProjects P1 
WHERENOT EXISTS (SELECT statusFROMProjects P2 WHEREP1.project_id = P2.project_id ANDSTATUS <> CASE WHEN step_nbr <= 1 THEN '完成' ELSE '等待' END);

虽然两者都能表达全称量化,但是与 HAVING 相比,使用了双重否定的 NOT EXISTS 代码看起来不是那么容易理解,这是它的缺点。

但是这种写法也有优点:

  1. 第一个优点是性能好。只要有一行满足条件,查询就会终止,不一定需要查询所有行的数据。而且还能通过连接条件使用“project_id”列的索引,这样查询起来会更快。
  2. 第二个优点是结果里能包含的信息量更大。如果使用 HAVING ,结果会被聚合,我们只能获取到项目 ID ,而如果使用 EXISTS ,则能把集合里的元素整体都获取到。

这里也可以把 NOT EXISTS 改写成 ALL 谓词的话,就不需要双重否定了。

-- 查找已经完成到工程1 的项目:使用ALL谓词解答
SELECT* 
FROMProjects P1 
WHERE'○' = ALL (SELECTCASE WHEN step_nbr <= 1 AND STATUS = '完成' THEN '○' WHEN step_nbr > 1 AND STATUS = '等待' THEN '○' ELSE '×' END FROMProjects P2 WHEREP1.project_id = P2.project_id );

如果对满足条件的行标记“○”,对不满足条件的行标记“×”,那么我们要查找的其实就是“所有行都被标记了○的项目”。这也是特征函数的一种应用。这种解法没有使用双重否定句,使用的是肯定句,理解起来很轻松。

这条查询语句有一个好处是查询结果中还包含集合的具体内容,但是因为需要对所有的行标记○或者×,所以性能没有使用 NOT EXISTS时好。

2.4 对列进行量化

ArrayTbl:

这里的问题是查询全是 1 的行。当然可以使用下面这种常规的解法:

--“列方向”的全称量化:不优雅的解答
SELECT *
FROM ArrayTbl
WHERE col1 = 1
AND col2 = 1
·
·
·
AND col10 = 1;

SQL 语言其实还准备了一个谓词,帮助我们进行“列方向”的量化。

--“列方向”的全称量化:优雅的解答
SELECT *
FROM ArrayTbl
WHERE 1 = ALL (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10);

此段代码在 PostgreSQL 中运行时会报错。要想成功运行,需将代码最后一行括号内的字符改为“values(col1),(col2),(col3),(col4),(col5),(col6),(col7),(col8),(col9),(col10)”。

反过来,如果想表达“至少有一个9”这样的存在量化命题,可以使用ALL的反义谓词 ANY。

-- 列方向的存在量化(1)
SELECT *
FROM ArrayTbl
WHERE 9 = ANY (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10);

或者也可以使用 IN 谓词代替 ANY 。

-- 列方向的存在量化(2)
SELECT *
FROM ArrayTbl
WHERE 9 IN (col1, col2, col3, col4, col5, col6, col7, col8, col9, col10);

我们一般都像 col1 IN (1, 2, 3) 这样来使用 IN 谓词,左边是列名,右边是值的列表,但是其实这种写法也是被允许的。但是需要注意的是,如果左边不是具体值而是 NULL ,这种写法就不行了。这种情况下,我们可以使用 COALESCE 函数。

-- 查询全是NULL 的行:正确的解法
SELECT *
FROM ArrayTbl
WHERE COALESCE(col1, col2, col3, col4, col5, col6, col7, col8, col9, col10) IS NULL

3. 问题

3.1 求质数

Numbers:

这是一张存储了 1 ~ 100 的所有自然数的表Numbers,现在需要做的就是求出这1 ~ 100 中的质数。

-- 解答:用NOT EXISTS 表达全称量化
SELECTnum AS prime 
FROMNumbers Dividend 
WHEREnum > 1 ANDNOT EXISTS (SELECT* FROMNumbers Divisor WHEREDivisor.num <= Dividend.num / 2 ANDDivisor.num <> 1 ANDMOD ( Dividend.num, Divisor.num ) = 0 )
ORDER BYprime;

“不能被1和它自身以外的所有自然数整除” 换而言之就是 “除了1和它自身以外,不存在能整除它的自然数”。因为约数不包含自身,所以约数必定小于等于自身值的一半,因此我们可以通过 Divisor.num <= Dividend.num/2 这样的条件缩小查找范围。此外,因为连接条件可以用到“num”列的索引,所以性能也比较好。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

管理系统权限总结

概念 功能权限和数据权限。 功能权限&#xff1a;用户是否能打开某一个网页&#xff0c;是否能点击编辑按钮等。数据权限&#xff1a;用户可以使用的数据范围。 用户 应用系统的具体操作者&#xff0c;用户可以自己拥有权限信息&#xff0c;可以归属于0&#xff5e;n个角色&…

使用VSCode连接远程服务器

1 效果展示 最近在使用云服务器开项目&#xff0c;发现VSCode的remote插件能远程连接服务器进行开发&#xff0c;这样就非常方便了。效果如下&#xff1a; 可以看到&#xff0c;这样操作&#xff0c;使得云端开发和本地开发几乎没什么不同&#xff0c;如果是云服务器就更方便了…

Vue脚手架报错:‘v-model‘ directives require no argument 解决方案

1、报错&#xff1a; v-model directives require no argument 截图 2、原因&#xff1a; ESLint对vetur进行了eslint检查 3、解决方法 ① 修改模板中使用v-show 将 v-model:show"show" 改为 v-model"show" ② vetur插件的作者给出了解决办法 我们可…

20201306吴龙灿第三章学习笔记

目录Ⅰ知识点归纳1.进程的概念什么是进程?进程的特征动态性并发性独立性异步性结构性程序和进程主要区别2.多任务处理系统(1)背景(2)多任务处理系统代码介绍3.进程同步(1)同步(2)进程唤醒与睡眠无效唤醒A 进程:B 进程:避免无效唤醒A 进程:Linux 内核的例子Ⅱ实践内容与…

docker jenkins升级以及失败处理

一、概述 jenkins是由docker安装的,目前的jenkins版本为2.356。然后jenkins右上角提示版本升级 点击了升级,升级完成后,需要重启一下。 然后就芭比Q了,访问jenkins出现504错误。查看docker日志,提示需要jdk升级到1.8。默认的jenkins的jdk版本为1.7,然后docker就开始一直…

督办管理系统——让企业工作落实到位

开展督查督办工作是企业在经营管理过程中的重要环节和管理手段&#xff0c;是企业办公室系统政务服务的一项重要工作。其具有间接性、权威性、实效性等特点。要加强企业督查督办工作&#xff0c;必须思想认识到位&#xff0c;充分把握督查督办工作原则;制度建设到位&#xff0c…

linux NTP同步时间后比实际时间慢8小时

1. issue ntp同步时间后比实际时间慢8小时 2. analysis 查询系统当前的时区设置 date -R&#xff0c;看到系统是 0000 时区&#xff0c;而中国统一采用北京所在的东8时区&#xff0c;由此造成了8小时的时间偏差。 3. solution 将PC ubuntu /usr/share/zoneinfo/Asia/Shanghai…

Django定义路由_子路由_函数视图

Django定义路由什么是路由&#xff0c;怎么去定义路由&#xff1f;添加路由Path 函数路由定义的痛点处理路由中的动态参数什么是路由&#xff0c;怎么去定义路由&#xff1f; 通常在我们创建项目包中&#xff0c;有个url.py的文件&#xff0c;我们需要去定义路由信息&#xff…

二叉树专项训练LeetCode

144. 二叉树的前序遍历 二叉树入门 递归 与 迭代 class Solution {List<Integer> ans new ArrayList<>();void dfs(TreeNode root){if(root null) {return;}ans.add(root.val);dfs(root.left);dfs(root.right);}public List<Integer> preorderTraversal(T…

【Golang开发面经】蔚来(两轮技术面)

文章目录一面1. channel 缓冲与非缓冲2. mysql引擎3. 索引如何建立&#xff1f;4. linux 如何看进程5. redis 字符串的底层6. 线程池理解7. 线程池的拒绝策略8.悲观锁&#xff0c;乐观锁9. HTTP 各个版本的区别10. HTTP2.0之前怎么实现服务器推送机制&#xff1f;11. websocket…

[操作系统] 启动

启动 一、通电 由于内存是随机存储器&#xff08;Random access memory&#xff0c;RAM&#xff09;&#xff0c;属于易失性存储器&#xff0c;未通电时&#xff0c;RAM中不会有任何内容&#xff0c;因此刚一通电&#xff0c;RAM不可能有任何实际信息。计算机硬件厂商在只读存…

信创浪潮下,看看大公司是如何建立数据安全保护体系的?

信创&#xff0c;即信息技术应用创新产业&#xff0c;它是数据安全、网络安全的基础&#xff0c;也是新基建的重要组成部分。信创涉及到的行业包括IT基础设施&#xff1a;CPU芯片、服务器、存储、交换机、路由器、各种云和相关服务内容&#xff0c;基础软件&#xff1a;数据库、…

1.ROS机器视觉:单目摄像头的调用与标定

(1条消息) ROS改错&#xff1a;vm虚拟机中调用摄像头失败_机械专业的计算机小白的博客-CSDN博客https://blog.csdn.net/wzfafabga/article/details/127204106?spm1001.2014.3001.5502 首先保证摄像头是可调用的。 1.安装usb_cam驱动 sudo apt-get install ros-melodic-usb-…

数据导入导出功能的测试点

【数据导入功能】 一、操作按钮校验 1、导入按钮生效 2、取消导入按钮生效 二、导入模板校验 1、文件数量 1&#xff09;不传模板&#xff1a;点确认时提示错误 2&#xff09;传模板&#xff1a;只支持单文件 or 还支持多文件同时导入 2、文件格式 只支持xlsx文件 or 还支…

HTML学生个人网站作业设计 学生大学生活网页设计作品 学生个人网页模板 简单个人主页成品 div+css个人网页制作

&#x1f329;️ 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f482; 作者主页: 【进入主页—&#x1f680;获取更多源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;HTML5网页期末作业 (1000套…

Java项目:ssh网上便利店系统

作者主页&#xff1a;夜未央5788 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 该项目分为前后台。非maven项目&#xff1b; 前台主要功能包括&#xff1a; 会员登录、注册、商品展示、加入购物车、会员中心、我的订单、我的地址…

【跟学C++】C++队列——queue类(Study13)

文章目录1、队列2、队列--queue类的使用2.1 实例化queue2.2 queue的成员函数3、优先级队列--priority_queue类的使用3.1 实例化priority_queue3.1 priority_queued的成员函数4、总结 【说明】 大家好&#xff0c;本专栏主要是跟学C内容&#xff0c;自己学习了这位博主【 AI菌】…

多测师肖sir_高级讲师_第2个月第21讲解jmeter安装

一、安装流程&#xff1a; 1、安装jdk &#xff08;linux&#xff0c;windows上&#xff09;&#xff0c;jdk编译java语言&#xff0c; 2、jdk环境配置&#xff0c;dos中java -version 查看jdk版本 3、下载jmeter包&#xff0c;解压&#xff0c;bin 目录 &#xff0c;jmeter.ba…

从零开始配置vim(25)——关于 c++ python 的配置

从9月份到国庆这段时间,因为得了女儿,于是回老家帮忙料理家事以及陪伴老婆和女儿。一时之间无暇顾及该系列教程的更新。等我回来的时候发现很多小伙伴私信我催更。在这里向支持本人这一拙劣教程的各位小伙伴表示真诚的感谢。言归正传,让我们开始吧 之前我们根据lua语言配置了…

(附源码)计算机毕业设计ssm电子购物商城

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…