SQL 中的 EXISTS

我们先从 SQL 中最基础的 WHERE 子句开始。

比如下面这条 SQL 语句:

很显然,在执行这条 SQL 语句的时候,DBMS 会扫描 Student 表中的每一条记录,然后把符合 Sdept = 'IS' 这个条件的所有记录筛选出来,并放到结果集里面去。也就是说 WHERE 关键字的作用就是判断后面的逻辑表达式的值是否为 True。如果为 True,则将当前这条记录(经过 SELECT 关键字处理后)放到结果集里面去,如果逻辑表达式的值为 False 则不放。

接下来看使用了 EXISTS 关键字的一条 SQL 语句:

这条 SQL 语句的作用,就是查找所有选修了 1 号课程的课程的学生,并显示他们的姓名。

我们先不管 EXISTS 关键字在其中起了什么作用,而是先来看子查询中的 WHERE 关键字后的表达式 Sno = Student.Sno AND Cno = '1'。

其中的 Sno = Student.Sno 是怎么一回事?

这就涉及到 SQL 中的不相关子查询与相关子查询了。

我们常见的带子查询的 SQL 语句是这样的:

首先通过子查询得到课名为 "数据结构" 的课程的课号,然后遍历 SC (选课)表中的每一条选课记录,若当前这条记录的课号为 "数据结构" 这门课的课号,则将这条记录的 Sno 列的值放到结果集里面去。最终我们可以得到所有选修了 "数据结构" 这门课的学生的学号。

这种类型的查询是先执行子查询,得到一个集合(或值),然后将这个集合(或值)作为一个常量带入到父查询的 WHERE 子句中去。如果单纯地执行子查询,也是可以成功的。

这种类型的查询,叫做 "不相关子查询"。

大多数情况下,不相关子查询已经够用了,但是如果有这样的一个查询要求:

子查询可以这么写:

那么问题来了, ? 处应该写什么值?

关键问题就是,? 处这个常量,并不是一个确定的值,而应该是不断地将 Student 表中的每一条记录中的 Sno 列的值代入此处,然后求出该 Sno 对应的平均成绩。我们需要的是输入一系列的值,然后得到一系列对应的输出。

这个时候,我们就要用到另一种嵌套查询,叫做 "相关子查询"。"相关子查询" 的意思就是,子查询中需要用到父查询中的值。

对于这个查询要求,我们可以使用以下 SQL 语句:

其工作原理就是,扫描父查询中数据来源(如 SC 表)中的每一条记录,然后将当前这条记录中的,在子查询中会用到的值代入到子查询中去,然后执行子查询并得到结果(可以看成是返回值),然后再将这个结果代入到父查询的条件中,判断父查询的条件表达式的值是否为 True,若为 True,则将当前 SC 表中的这条记录(经过 SELECT 处理)后放到结果集中去。若为 False 则不放。

在这个例子中,父查询先从 SC 表中取出第一条记录,然后将当前这条记录的 Sno 列的值(如 95001)代入到子查询中,求出学号为 95001 的学生选修的所有课程的平均分(如 80 分)。然后将这个 80 作为 Grade >= 后面的值代入,若 SC 表中的第一条记录的 Grade 列的值为 90,那么 Grade >= 80 这个条件表达式的值为 True,则将当前这条记录中的 Cno 列的值(如1)放入结果集中去。以此类推,遍历 SC 表中的所有记录,即可得到每个超过学生超过他/她所有课程平均分的课程的课号了。

判断是否是 "相关子查询" 也很简单,只要子查询不能脱离父查询单独执行,那么就是 "相关子查询"。

知道了 "相关子查询" 的概念之后,我们就可以回来了解 EXISTS 关键字的作用了。它的作用,就是判断子查询得到的结果集是否是一个空集,如果不是,则返回 True,如果是,则返回 False。EXISTS 本身就是 "存在" 的意思,用我们可以理解的话来说,就是如果在当前的表中存在符合条件的这样一条记录,那么返回 True,否则返回 False。

为了方便,我们再次放出这条 SQL 语句:

在这个查询中,首先会取出 Student 表中的第一条记录,得到其 Sno 列(因为在子查询中用到了)的值(如 95001),然后将该值代入到子查询中。若能找到这样的一条记录,那么说明学号为 95001 的学生选修了 1 号课程。因为能找到这样的一条记录,所以子查询的结果不为空集,那么 EXISTS 会返回 True,从而使 Student 表中的第一条记录中的 Sname 列的值被放入结果集中去。以此类推,遍历 Student 表中的所有记录后,就能得到所有选修了 1 号课程的学生的姓名。

与 EXISTS 关键字相对的是 NOT EXISTS,作用与 EXISTS 正相反,当子查询的结果为空集时,返回 True,反之返回 False。也就是所谓的 "若不存在"。

对于下面的查询要求,只能通过 NOT EXISTS 关键字来实现,因为 SQL 中并未直接提供关系代数中的除法功能。

可以通过以下步骤的思路来实现:

STEP1:先取 Student 表中的第一个元组,得到其 Sno 列的值。

STEP2:再取 Course 表中的第一个元组,得到其 Cno 列的值。

STEP3:根据 Sno 与 Cno 的值,遍历 SC 表中的所有记录(也就是选课记录)。若对于某个 Sno 和 Cno 的值来说,在 SC 表中找不到相应的记录,则说明该 Sno 对应的学生没有选修该 Cno 对应的课程。

STEP4:对于某个学生来说,若在遍历 Course 表中所有记录(也就是所有课程)后,仍找不到任何一门他/她没有选修的课程,就说明此学生选修了全部的课程。

STEP5:将此学生放入结果元组集合中。

STEP6:回到 STEP1,取 Student 中的下一个元组。

STEP7:将所有结果元组集合显示。

根据以上思路,可以写出 SQL 语句:

其中第一个 NOT EXISTS 对应 STEP4,第二个 NOT EXISTS 对应 STEP3。

同理,对于类似的查询要求

可以使用 SQL 语句:

对于查询要求

可以使用 SQL 语句:

相关推荐
数据智能老司机1 小时前
CockroachDB权威指南——SQL调优
数据库·分布式·架构
数据智能老司机1 小时前
CockroachDB权威指南——应用设计与实现
数据库·分布式·架构
数据智能老司机2 小时前
CockroachDB权威指南——CockroachDB 模式设计
数据库·分布式·架构
数据智能老司机20 小时前
CockroachDB权威指南——CockroachDB SQL
数据库·分布式·架构
数据智能老司机21 小时前
CockroachDB权威指南——开始使用
数据库·分布式·架构
松果猿21 小时前
空间数据库学习(二)—— PostgreSQL数据库的备份转储和导入恢复
数据库
无名之逆21 小时前
Rust 开发提效神器:lombok-macros 宏库
服务器·开发语言·前端·数据库·后端·python·rust
s91236010121 小时前
rust 同时处理多个异步任务
java·数据库·rust
数据智能老司机21 小时前
CockroachDB权威指南——CockroachDB 架构
数据库·分布式·架构
hzulwy1 天前
Redis常用的数据结构及其使用场景
数据库·redis