| 一、 |
带有IN谓词的子查询 |
| |
带有IN谓词的子查询是指父查询与子查询之间用IN进行连接,判断某个属性列值是否在子查询的结果中。由于在嵌套查询中,子查询的结果往往是一个集合,所以谓词IN是嵌套查询中最经常使用的谓词。 |
| 例37 |
查询与“刘晨”在同一个系学习的学生 |
| |
详细信息… |
| |
查询与“刘晨”在同一个系学习的学生,可以首先确定“刘晨”所在系名,然后再查找所有在该系学习的学生。所以可以分步来完成此查询: ①确定“刘晨”所在系名
SELECT Sdept FROM Student WHERE Sname='刘晨';
结果为:
Sdept -------- IS
②查找所有在IS系学习的学生。
SELECT Sno, Sname, Sdept FROM Student WHERE Sdept='IS';
结果为:
Sno Sname Sdept ------- ------- ------- 95001 刘晨 IS 95004 张立 IS
分步写查询毕竟比较麻烦,上述查询实际上可以用子查询来实现,即将第一步查询嵌入到第二步查询中,用以构造第二步查询的条件。SQL语句如下:
SELECT Sno, Sname, Sdept FROM Student WHERE Sdept IN SELECT Sdept FROM Student WHERE Sname='刘晨';
本例中的查询也可以用我们前面学过的表的自身连接查询来完成:
SELECT Sno, Sname, Sdept FROM Student S1, Student S2 WHERE S1.Sdept = S2.Sdept AND S2.Sname='刘晨';
可见,实现同一个查询可以多种方法,当然不同的方法其执行效率可能会有差别,甚至会差别很大。
|
| 例38 |
查询选修了课程名为'信息系统'的学生学号和姓名 |
| |
详细信息… |
| |
SELECT Sno, Sname FROM Student WHERE Sno IN SELECT Sno FROM SC WHERE Cno IN SELECT Cno FROM Course WHERE Cname='信息系统'
Sno Sname ------- -------- 95001 李勇 95002 刘晨
本查询同样可以用连接查询实现:
SELECT Sno, Sname FROM Student, SC, Course WHERE Student.Sno=SC.Sno AND SC.Cno=Course.Cno AND Course.Cname='信息系统';
例37和例38中的各个子查询都只执行一次,其结果用于父查询,子查询的查询条件不依赖于父查询,这类子查询称为不相关子查询。不相关子查询是最简单的一类子查询。
|
| 二、 |
带有比较运算符的子查询 |
| |
带有比较运算符的子查询是指父查询与子查询之间用比较运算符进行连接。当用户能确切知道内层查询返回的是单值时,可以用>、 <、 =、 >=、<=、!=或<>等比较运算符。 |
| |
例如: |
| |
详细信息… |
| |
在例37中,由于一个学生只可能在一个系学习,也就是说内查询刘晨所在系的结果是一个唯一值,因此该查询也可以用比较运算符来实现,其SQL语句如下:
SELECT Sno, Sname, Sdept FROM Student WHERE Sdept = SELECT Sdept FROM Student WHERE Sname='刘晨'; 需要注意的是,子查询一定要跟在比较符之后,下列写法是错误的: SELECT Sno, Sname, Sdept FROM Student WHERE (SELECT Sdept FROM Student WHERE Sname='刘晨') = Sdept;
例38中信息系统的课程号是唯一的,但选修该课程的学生并不只一个,所以例38也可以用=运算符和IN谓词共同完成:
SELECT Sno, Sname FROM Student WHERE Sno IN SELECT Sno FROM SC WHERE Cno = SELECT Cno FROM Course WHERE Cname='信息系统'
|
| 三、 |
带有ANY或ALL谓词的子查询 |
| |
子查询返回单值时可以用比较运算符。而使用ANY或ALL谓词时则必须同时使用比较运算符。其语义为:
> ANY 大于子查询结果中的某个值 < ANY 小于子查询结果中的某个值 >= ANY 大于等于子查询结果中的某个值 <= ANY 小于等于子查询结果中的某个值 <= ANY 小于等于子查询结果中的某个值 = ANY 等于子查询结果中的某个值 != ANY或<> ANY 不等于子查询结果中的某个值 > ALL 大于子查询结果中的所有值 < ALL 小于子查询结果中的所有值 >= ALL 大于等于子查询结果中的所有值 <= ALL 小于等于子查询结果中的所有值 <= ALL 小于等于子查询结果中的所有值 = ALL 等于子查询结果中的所有值(通常没有实际意义) != ALL或<> ALL 不等于子查询结果中的任何一个值 |
| 例39 |
查询其他系中比IS系任一学生年龄小的学生名单 |
| |
详细信息… |
| |
SELECT Sname, Sage FROM Student WHERE Sage (SELECT Sage FROM Student WHERE Sdept="IS" ) AND Sdept <> 'IS' ORDER BY Sage DESC;
本查询实际上也可以用集函数实现。
SELECT Sname, Sage FROM Student WHERE Sage > (SELECT MAX(Sage) FROM Student WHERE Sdept='IS') AND Sdept <> 'IS' ORDER BY Sage DESC;
事实上,用集函数实现子查询通常比直接用ANY或ALL查询效率要高。ANY与ALL与集函数的对应关系如表3-4所示。
| |
表3-4 ANY、ALL谓词与集函数及IN谓词的等价转换关系 |
|
| |
= |
<>或!= |
< |
<= |
> |
>= |
| ───── |
───── |
───── |
───── |
───── |
───── |
───── |
| ANY |
IN |
-- |
<MAX |
<=MAX |
>MIN |
>= MIN |
| ALL |
-- |
NOT IN |
<MIN |
<=MIN |
>MAX |
>= MAX |
|
| 四、 |
带有EXISTS谓词的子查询 |
| |
EXISTS代表存在量词彐。带有EXISTS谓词的子查询不返回任何实际数据,它只产生逻辑真值“true"或逻辑假值“false"。 |
| 例40 |
查询所有选修了1号课程的学生姓名 |
| |
详细信息… |
| |
查询所有选修了1号课程的学生姓名涉及Student关系和SC关系,我们可以在Student关系中依次取每个元组的Sno值,用此Student.Sno值去检查SC关系,若SC中存在这样的元组,其SC.Sno值等于用来检查的Student.Sno值,并且其SC.Cno='1',则取此Student.Sname送入结果关系。将此想法写成SQL语句就是:
SELECT Sname FROM Student WHERE EXISTS (SELECT * FROM SC WHERE Sno=Student.Sno AND Cno='1');
使用存在量词EXISTS后,若内层查询结果非空,则外层的WHERE子句返回真值,否则返回假值。
由EXISTS引出的子查询,其目标列表达式通常都用*,因为带EXISTS的子查询只返回真值或假值,给出列名亦无实际意义。
这类查询与我们前面的不相关子查询有一个明显区别,即子查询的查询条件依赖于外层父查询的某个属性值(在本例中是依赖于Student表的Sno值),我们称这类查询为相关子查询(Correlated Subquery)。求解相关子查询不能象求解不相关子查询那样,一次将子查询求解出来,然后求解父查询。相关子查询的内层查询由于与外层查询有关,因此必须反复求值。从概念上讲,相关子查询的一般处理过程是:
首先取外层查询中Student表的第一个元组,根据它与内层查询相关的属性值(即Sno值)处理内层查询,若WHERE子句返回值为真(即内层查询结果非空),则取此元组放入结果表;然后再检查Student表的下一个元组;重复这一过程,直至Student表全部检查完毕为止。
本例中的查询也可以用连接运算来实现,读者可以参照有关的例子,自己给出相应的SQL语句。 与EXISTS谓词相对应的是NOT EXISTS谓词。使用存在量词NOT EXISTS后,若内层查询结果为空,则外层的WHERE子句返回真值,否则返回假值。
|
| 例41 |
查询所有未修1号课程的学生姓名 |
| |
详细信息… |
| |
SELECT Sname FROM Student WHERE NOT EXISTS (SELECT * FROM SC WHERE Sno=Student.Sno AND Cno='1');
一些带EXISTS或NOT EXISTS谓词的子查询不能被其他形式的子查询等价替换,但所有带IN谓词、比较运算符、ANY和ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换。例如带有IN谓词的例37可以用如下带EXISTS谓词的子查询替换:
SELECT Sno, Sname, Sdept FROM Student S1 WHERE EXISTS SELECT * FROM Student S2 WHERE S2.Sdept=S1.Sdept AND S2.Sname='刘晨';
由于带EXISTS量词的相关子查询只关心内层查询是否有返回值,并不需要查具体值,因此其效率并不一定低于不相关子查询,甚至有时是最高效的方法。
SQL语言中没有全称量词∨(For all)。因此必须利用谓词演算将一个带有全称量词的谓词转换为等价的带有存在量词的谓词:。
|
| 例42 |
查询选修了全部课程的学生姓名 |
| |
详细信息… |
| |
由于没有全称量词,我们将题目的意思转换成等价的存在量词的形式:查询这样的学生姓名,没有一门课程是他不选的。该查询涉及三个关系,存放学生姓名的Student表,存放所有课程信息的Course表,存放学生选课信息的SC表。其SQL语句为:
SELECT Sname FROM Student WHERE NOT EXISTS (SELECT * FROM Course WHERE NOT EXISTS (SELECT * FROM SC WHERE Sno=SC.Sno AND Cno=C.Cno);
SQL语言中也没有蕴函(Implication)逻辑运算。因此也必须利用谓词演算将一个逻辑蕴函的谓词转换为等价的带有存在量词的谓词:
|
| 例43 |
查询至少选修了学生95002选修的全部课程的学生号码。 |
| |
详细信息… |
| |
本题的查询要求可以做如下解释,查询这样的学生,凡是95002选修的课,他都选修了。换句话说,若有一个学号为x的学生,对所有的课程y,只要学号为95002的学生选修了课程y,则x也选修了y;那么就将他的学号选出来。它所表达的语义为:不存在这样的课程y,学生95002选修了y,而学生x没有选。用SQL语言可表示如下:
SELECT DISTINCT Sno FROM SC SCX WHERE NOT EXISTS (SELECT * FROM SC SCY WHERE SCY.Sno='95002' AND NOT EXISTS (SELECT * FROM SC SCZ WHERE SCZ.Sno=SCX.Sno AND SCZ.Cno=SCY.Cno);
|