SQL中的EXISTS
SQL中的EXISTS与SQL中的NOT EXISTS
从SQL中基础的WHERE字句开始
有一学生信息表
1 | SELECT Sno, Sname FROM Student WHERE Sdept = 'IS'; |
很显然,在执行这条 SQL语句的时候,DBMS会扫描 Student表中的每一条记录,然后把符合 Sdept = 'IS'这个条件的所有记录筛选出来,并放到结果集里面去。也就是说 WHERE关键字的作用就是判断后面的逻辑表达式的值是否为 True。如果为 True,则将当前这条记录(经过SELECT关键字处理后)放到结果集里面去,如果逻辑表达式的值为 False则不放。
使用EXISTS关键字
1 | SELECT Sname FROM Student WHERE EXISTS |
这条SQL语句的作用,就是查找所有选修了 1 号课程的课程的学生,并显示他们的姓名。
我们先不管EXISTS关键字在其中起了什么作用,而是先来看子查询中的WHERE 关键字后的表达式 :
1 | Sno = Student.Sno AND Cno = '1' |
其中的 Sno = Student.Sno是怎么一回事?这就涉及到 SQL 中的不相关子查询与相关子查询了
不相关子查询与相关子查询
不相关子查询
我们常见的带子查询的SQL语句是这样的:
1 | SELECT Sno FROM SC WHERE Cno IN |
查询过程如下:
- 首先通过子查询得到课名为 “数据结构” 的课程的课号
然后遍历
SC(选课)表中的每一条选课记录- 若当前这条记录的课号为 “数据结构” 这门课的课号,则将这条记录的 Sno 列的值放到结果集里面去。
- 最终我们可以得到所有选修了 ”数据结构“ 这门课的学生的学号。
这种类型的查询,叫做”不相关子查询”:
这种类型的查询是先执行子查询,得到一个集合(或值),然后将这个集合(或值)作为一个常量带入到父查询的 WHERE 子句中去。如果单纯地执行子查询,也是可以成功的。
相关子查询
大多数情况下,不相关子查询已经够用了,但是如果有这样的一个查询要求:
1 | 查询每个学生超过自身选修的所有课程的平均成绩的课程的课程号 |
子查询我们可以先查询出每个学生自身选修的所有课程的平均成绩:
1 | SELECT AVG(Grade) FROM SC WHERE Sno = ? |
那么问题来了,WHERE后面应该怎么写?也就Sno=?这里面怎么填
关键问题就是,? 处这个常量,并不是一个确定的值,而应该是不断地将 Student表中的每一条记录中的 Sno列的值代入此处,然后求出该Sno对应的平均成绩。我们需要的是输入一系列的值,然后得到一系列对应的输出。
这个时候,我们就要用到另一种嵌套查询,叫做 “相关子查询”。“相关子查询” 的意思就是,子查询中需要用到父查询中的值。
对于这个查询要求,我们可以使用以下SQL 语句:
1 | SELECT Cno FROM SC X WHERE Grade >= |
其工作原理就是:
- 先扫描父查询中数据来源(如
SC表)中的每一条记录 - 然后将当前这条记录中的,在子查询中会用到的值代入到子查询中去
- 接着执行子查询并得到结果(可以看成是返回值)
- 然后再将这个结果代入到父查询的条件中,判断父查询的条件表达式的值是否为
True- 若为
True,则将当前SC表中的这条记录(经过SELECT处理)后放到结果集中去 - 若为
False则不放
- 若为
在这个例子中:
父查询先从 SC 表中取出第一条记录(如 95001)
然后将当前这条记录的 Sno 列的值(95001)代入到子查询中,求出学号为 95001 的学生选修的所有课程的平均分(如 80 分)
然后将这个 80 作为
Grade >=后面的值代入- 若
SC表中的第一条记录的Grade列的值为 90,那么Grade >= 80这个条件表达式的值为 True,则将当前这条记录中的Cno列的值(如1)放入结果集中去。
- 若
以此类推,遍历
SC表中的所有记录,即可得到每个超过学生超过他/她所有课程平均分的课程的课号了。
判断是否是 “相关子查询” 也很简单,只要子查询不能脱离父查询单独执行,那么就是 “相关子查询”。
EXISTS关键字的作用
它的作用,就是判断子查询得到的结果集是否是一个空集,如果不是,则返回 True,如果是,则返回 False。EXISTS 本身就是 “存在” 的意思,用我们可以理解的话来说,就是如果在当前的表中存在符合条件的这样一条记录,那么返回True,否则返回 False。
有一条语句:
1 | SELECT Sname FROM Student WHERE EXISTS( |
在这个查询中:
- 首先会取出
Student表中的第一条记录,得到其 Sno 列(因为在子查询中用到了)的值(如 95001) - 然后将该值代入到子查询中。若能找到这样的一条记录,那么说明学号为 95001 的学生选修了 1 号课程。
- 因为能找到这样的一条记录,所以子查询的结果不为空集,那么
EXISTS会返回True,从而使Student表中的第一条记录中的Sname列的值被放入结果集中去。 - 以此类推,遍历
Student表中的所有记录后,就能得到所有选修了 1 号课程的学生的姓名。
- 因为能找到这样的一条记录,所以子查询的结果不为空集,那么
NOT EXISTS关键字
与EXISTS关键字相对的是NOT EXISTS,作用与 EXISTS正相反,当子查询的结果为空集时,返回 True,反之返回 False。也就是所谓的 ”若不存在“。
对于下面的查询要求,只能通过NOT EXISTS关键字来实现,因为 SQL 中并未直接提供关系代数中的除法功能。
- 查询选修了全部课程的学生的姓名
可以通过以下步骤的思路来实现:
- 先取
Student表中的第一个元组,得到其Sno列的值。也就是获取到学生对应的学号 - 再取
Course表中的第一个元组,得到其Cno列的值。也就是获取到课程对应的课程号 根据
Sno与Cno的值,遍历SC表中的所有记录(也就是选课记录)。若对于某个Sno和Cno的值来说,在SC表中找不到相应的记录,则说明该Sno对应的学生没有选修该Cno对应的课程。对于某个学生来说,若在遍历
Course表中所有记录(也就是所有课程)后,仍找不到任何一门他/她没有选修的课程,就说明此学生选修了全部的课程。- 将此学生放入结果元组集合中
- 回到第一步,取
Student中的下一个元组 - 将所有结果元组集合显示
1 | SELECT Sname FROM Student WHERE NOT EXISTS |
其中第一个 NOT EXISTS对第四步,第二个 NOT EXISTS对应第三步。
同理,对于类似的查询要求
- 查询被所有学生选修的课程的课名
1 | SELECT Cname FROM Course WHERE NOT EXISTS |
- 查询选修了95001号学生选修的全部课程的学生的学号
1 | SELECT DISTINCT Sno FROM SC X WHERE NOT EXISTS |
参考: