3.4.3 嵌套查询
在SQL语言中,一个SELECT…FROM…WHERE语句称为一个查询块。将一个查询块嵌套在另一个查询块的WHERE子句或HAVING子句的条件中的查询称为嵌套查询。嵌套查询也是涉及多表的查询,其中外层查询称为父查询,内层查询称为子查询。在子查询中还可以嵌套其他子查询,即允许多层嵌套查询。注意:子查询中不允许使用ORDER BY子句。
嵌套查询使用户可以用多个简单查询构造复杂的查询,从而增强SQL的功能。
1)单值嵌套查询
单值嵌套查询就是通过子查询返回一个单一的值。当子查询返回的是单值时,可以使用比较运算符(>、<、=、<=、>=、!=或<>等)将父查询的属性与该子查询的结果连接起来参与表达式的相关运算。
【例3.52】 查询与刘敏在同一个学院的学生姓名、性别、出生日期。
先分步来完成此查询,然后再构造嵌套查询。
①确定“刘敏”所在学院。
SELECT Sdept
FROM Student
WHERE Sname=刘敏;
假设查询结果为“计算机”。
②查找所有在“计算机”学院的学生姓名、性别、出生日期。
SELECT Sname NAME,Ssex SEX,Sbirthday BIRTHDAY
FROM Student
WHERE Sdept=计算机;
可能的查询结果为:
将第一步查询嵌入到第二步查询的条件中,构造嵌套查询如下:
本例中,子查询的查询条件不依赖于父查询,子查询可以单独运行,这样的嵌套查询称为不相关子查询。如果子查询的查询条件依赖于父查询,子查询不能单独运行,这样的嵌套查询称为相关子查询。不相关子查询的求解方法是由里向外处理,即先执行子查询,子查询的结果用于建立其父查询的查找条件。
实现同一个查询请求可以有多种方法,例如,本例中的查询也可以用表的自身连接来完成:
SELECT S2.Sname NAME,S2.Ssex SEX,S2.Sbirthday BIRTHDAY
FROM Student S1,Student S2
WHERE S1.Sname=刘敏AND S2.Sdept=S1.Sdept;
【例3.53】 找出每个学生大于等于他自己所选课程平均成绩的学号、课程号。
X是SC的别名,内层查询是求一个学生所有选修课程平均成绩,至于是哪个学生的平均成绩,要看参数X.Sno的值,而该值是与父查询相关的,因此这类查询称为相关子查询。
这个语句的执行过程采用下述步骤:
①从外层查询中取出SC一个元组,将该元组的Sno值(不妨假设为“201818101”)传送给内层查询:
SELECT AVG(Grade)
FROM SC Y
WHERE Y.sno=201818101;
②执行内层查询,假设得到值82,用该值代替内层查询,得到外层查询:
SELECT Sno,Cno
FROM SC X
WHERE Grade>=82
③执行这个查询,可能得到(201818101,C004)。
然后外层查询取出下一个元组重复做上述步骤的处理,直到外层的SC中的所有元组全部处理完毕。
2)多值嵌套查询
如果子查询返回的结果是若干元组的集合,这样的嵌套查询称为多值嵌套查询。多值嵌套查询经常使用IN、ANY、ALL、SOME等关键字。
(1)使用IN关键字
【例3.54】 查询计算机学院中选修了课程的学号。
由于本例中子查询的查询条件不依赖于父查询,所以是不相关子查询,且子查询返回的是多个Sno值,因此该子查询的前面不能用等号(=)。
(2)使用ANY、ALL和SOME关键字
ANY、ALL和SOME关键字必须与比较运算符同时使用,其基本语法格式如下:
说明:
scalar_expression:任意有效的表达式;
SOME和ANY是等效的;
subquery:包含某属性列结果集的子查询,所返回属性列的数据类型必须是与scalar_expression相同的数据类型;子查询需用括号括起来。
ANY和ALL关键字的用法和功能见表3-11。
表3-11 ANY和ALL关键字的用法和功能
【例3.55】 查询非计算机学院中比计算机学院所有学生年龄都小的学生姓名和年龄。
本例也可以用聚合函数来实现,首先用子查询找出计算机学院中的最小年龄(不妨假设为19),然后在父查询中查找所有非计算机学院且年龄小于19岁的学生,语句如下:
事实上,用聚合函数实现子查询通常比直接用ANY或ALL查询效率高。ANY、ALL与聚合函数的对应关系见表3-12。
表3-12 ANY、ALL与聚合函数的对应关系
3)带有EXISTS关键字的子查询
EXISTS代表存在量词,带有EXISTS关键字的子查询不返回任何数据,只产生逻辑真值TRUE或逻辑假值FALSE。EXISTS关键字后是一个子查询,如果子查询至少返回一行,则EXISTS表达式返回逻辑真值,否则返回逻辑假值。因为带EXISTS的子查询只返回逻辑真值或逻辑假值,给出列名无实际意义,所以由EXISTS引出的子查询,其目标列表达式通常都使用星号(*)来代替。
【例3.56】 查询所有选修了“C001”课程的学生姓名。
一般情况下,带有EXISTS关键字的嵌套查询往往都是相关子查询,因为在内层查询的查询条件中大多会使用外层查询表中的元组的属性值,本例就是一个相关子查询,其内层查询的查询条件中使用了外层查询表Student中的元组的Sno值。
【例3.57】 查询没有选修“C001”课程的学生姓名。
EXISTS前可以加关键字NOT。NOT EXISTS与EXISTS使用方法相同,返回的结果正好相反。如果子查询没有返回任何行,则NOT EXISTS表达式返回逻辑真值,否则返回逻辑假值。
【例3.58】 查询没有选修课程的女生姓名。
EXISTS关键字可以和条件表达式一起使用。
【例3.59】 查询选修了全部课程的学生姓名。
由于带EXISTS谓词的相关子查询只关心内层查询是否有返回值,并不需要查询的具体值,因此其效率并不一定低于不相关子查询,有时是高效的方法。