Alex_McAvoy

想要成为渔夫的猎手

SQL 嵌套查询

【概述】

一个 SELECT-WHERE 语句称为一个查询块,将一个查询块嵌套在另一个查询块的 WHERE 子句或 HAVING 短语的条件中,称为嵌套查询

在嵌套查询这种,上层的查询块称为外层查询父查询,下层的查询块称为内层查询子查询

如下例,就是一个嵌套查询

1
2
3
4
5
6
7
SELECT Sname      /*父查询*/
FROM student
WHERE Sno IN(     /*子查询*/
SELECT Sno
FROM SC
WHERE Cno='1'
);

SQL 允许多层嵌套查询,即一个子查询中可以嵌套其他子查询,使得通过使用多个简单的查询构成复杂的查询,从而加强了 SQL 的查询能力,这种层层嵌套的方式,反映了 SQL 语言的结构化

要注意的是,有些嵌套查询可以通过连接运算替代,同时子查询不能使用 ORDER BY 子句ORDER BY 子句只能对最终的结果排序

【带有 IN 的子查询】

在嵌套查询中,子查询的结果往往是一个集合,所以谓词 IN 是嵌套查询中最常用的谓词

例如,查询与张三在一个系的学生

该要求可以分步来完成,第一步确定张三所在的系名

第二步根据所查的系名来查找所有在 CS 系的学生

将第一步嵌入第二步中,即可构造嵌套查询

该例中,子查询的查询条件不依赖父查询,该类查询称为不相关子查询,一般处理的方法是根据要求从里向外逐层处理,用子查询的结果用于建立其父查询的查询条件

除上述两种方法外,该例还可以使用自身连接来完成

可见,对于嵌套查询来说,有些可以使用连接查询来替代,只是 SQL 语言的书写和执行效率不同

  • 如果是嵌套查询:首先会在内层中筛选得到一张满足条件的小表,再到外面的一层表进行查找,运算量较少

  • 如果是连接查询:首先会进行笛卡尔积运算,生成一张大表后再去进行选择,运算量较大

对于成千上万行的大型数据库,使用上述两种不同的方法运算效率差别极大,也就是说,使用嵌套查询可以大幅度提高查询效率,此外,嵌套查询逐步求解层次清楚、易于构造,具有结构化程序设计的优点

【带有比较运算符的子查询】

带有比较运算符的子查询是指父查询与子查询间用比较运算符连接,当用户确切知道子查询返回的是单值时,可以使用 ><>=<==!=<> 等比较运算符

例如,找出每个学生超过其选修课程平均成绩的课程号

该例中,子查询的查询条件依赖父查询,该类查询称为相关子查询,一般处理的方法是:

  1. 取父查询中表的第一个元组,根据与子查询相关的属性值处理子查询,若 WHERE 子句返回为真,则将该元组放入结果表中
  2. 取父查询中表中的下一个元组,重复过程 1,直到父查询全部检查完为止

【带有 ANY、ALL 的子查询】

语义

子查询返回单值时可以用比较运算符,当返回多值时要用 ANY 谓词(有的系统使用 SOME)或 ALL 谓词

ANY 代表任意一个值,ALL 代表所有值,当使用 ANYALL 时,必须同时使用比较运算符,其语义如下表所示:

谓词与修饰符 语义
>ANY 大于子查询结果中的某个值
>ALL 大于子查询结果中的所有值
<ANY 小于子查询结果中的某个值
<ALL 小于子查询结果中的所有值
>=ANY 大于等于子查询结果中的某个值
>=ALL 大于等于子查询结果中的所有值
<=ANY 小于等于子查询结果中的某个值
<=ALL 小于等于子查询结果中的所有值
=ANY 等于子查询结果中的某个值
=ALL 等于子查询结果中的所有值
!=(或<>)ANY 不等于子查询结果中的某个值
!=(或<>)ALL 不等于子查询结果中的任何一个值

例如,查询其他系中比 CS 系所有学生年龄都小或相等的学生姓名、年龄

聚集函数

实际上,用聚集函数实现子查询通常比直接用 ANYALL 的查询效率高,ANYALL 与聚集函数的对应关系如下表

= != 或 <> < <= > >=
ANY IN <MAX <=MAX >MIN >=MIN
ALL NOT IN <MIN <=MIN >MAX >=MAX

【带有 EXISTS 的子查询】

EXISTS 谓词

EXISTS 谓词即数理逻辑中的存在量词 $\exists$,所有带 IN、比较运算符、ANYALL 谓词的子查询都能用带 EXISTS 谓词的子查询等价替换

带有 EXISTS 谓词的子查询不返回任何数,只产生逻辑真值 truefalse

  • 若子查询结果非空,则外层的 WHERE 子句返回 true
  • 若子查询结果为空,则外层的 WHERE 子句返回 false

由于 EXISTS 返回逻辑真值,给出的列名无实际意义,因此尤其引出的子查询,其目标列表表达式通常都用 *

例如,查询选修 1 号课程的学生姓名

EXISTS 谓词通常也会与 NOT 搭配使用

  • 若子查询结果非空,则外层的 WHERE 子句返回 false
  • 若子查询结果为空,则外层的 WHERE 子句返回 true

例如,查询没有选修 1 号课程的学生姓名

全称量词的实现

SQL 中没有全称量词 $\forall$,但是利用谓词演算的方法,可以将带有全称量词的谓词转换为等价的带有存在量词的谓词,即:

例如,查询选修了全部课程的学生的学号、姓名

由于没有全称量词,转换为等价的用存在量词的形式,即查询没有一门课程是他不选修的学生的学号、姓名

1
2
3
4
5
6
7
8
9
10
11
12
SELECT Sname
FROM student
WHERE NOT EXISTS(
SELECT *
FROM course
WHERE NOT EXISTS(
SELECT *
FROM sc
WHERE Sno = student.Sno AND
Cno = course.Cno
)
);

逻辑蕴含的实现

SQL 中同样没有逻辑蕴含 $\rightarrow$,利用谓词演算,可以将逻辑蕴含进行等价转换,即

例如,查询至少选修了学号为 1 号的学生选修的全部课程的学生号

该查询用逻辑蕴含表达为:查询学号为 x 的学生,对所有课程 y,只要学号为 1 的学生选修了课程 y,则 x 也选修了 y

其中,形式化表达如下:

  • 用 $p$ 表示谓词 “学号为 1 的学生选修了课程 y”
  • 用 $q$ 表示谓词 “学生 x 选修了课程 y”

则上述查询为:$(\forall y)p \rightarrow q$

可以发现是一个蕴含式,将其进行等价变换:

变换后的语义为:不存在这样的课程 y,学号为 1 的学生选修了 y,而 学生 x 没有选

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT DISTINCT Sno
FROM sc sc_x /*代表学生x*/
WHERE NOT EXISTS(
SELECT *
FROM sc sc_y /*代表学生y*/
WHERE sc_y.Sno = 1 AND
NOT EXISTS(
SELECT *
FROM sc sc_z
WHERE sc_z.Sno = sc_x.Sno AND
sc_z.Cno = sc_y.Cno
)
);
感谢您对我的支持,让我继续努力分享有用的技术与知识点!