嵌套查询—(4)带有EXISTS谓词的子查询(难点)
1、EXISTS谓词:存在量词
(从外层取出一个元组到内层中去比较)
(1)带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”。
若内层查询结果非空,则外层的WHERE子句返回真值;
若内层查询结果为空,则外层的WHERE子句返回假值。
(2)由EXISTS引出的子查询,其目标列表达式通常都用 * ,因为带EXISTS的子查询只返回真值或假值,给出列名无实际意义。
2、NOT EXISTS谓词
若内层查询结果非空,则外层的WHERE子句返回假值;
若内层查询结果为空,则外层的WHERE子句返回真值。
【例3.60】查询所有选修了1号课程的学生姓名。
方法一:连接查询
SELECT Sname
FROM Student,SC
WHERE Student.Sno=SC.Sno AND Cno='1';
方法二:EXISTS查询(相关子查询)
思路分析:
1、本查询涉及Student和SC关系
2、在Student中依次取每个元组的Sno值,用此值去检查SC表
3、若SC中存在这样的元组,其Sno值等于Student.Sno值,并且其Cno= ‘1’,则取此Student.Sname送入结果表
SELECT Sname
FROM Student
WHERE EXISTS
(SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno='1');
【例3.61】查询没有选修1号课程的学生姓名。
SELECT Sname
FROM Student
WHERE NOT EXISTS
(SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno='1');
不同形式的查询间的替换(EXISTS的能力更强)
(1)带IN谓词、比较运算符、ANY和ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换;
(2)一些带EXISTS或NOT EXISTS谓词的子查询不能被其他形式的子查询等价替换。
【例3.55】查询与“刘晨”在同一个系学习的学生。
方法一:不相关子查询
SELECT Sno,Sname,Sdept
FROM Student
WHERE Sdept IN
(SELECT Sdept
FROM Student
WHERE Sname='刘晨');
方法二:自身连接查询
SELECT S1.Sno,S1.Sname,S1.Sdept
FROM Student S1,Student S2
WHERE S1.Sdept=S2.Sdept AND S2.Sname='刘晨';
方法三:用带EXISTS谓词的子查询替换:
SELECT Sno,Sname,Sdept
FROM Student S1
WHERE EXISTS
(SELECT *
FROM Student S2
WHERE S2.Sdept=S1.Sdept AND S2.Sname='刘晨');
执行过程:(1)取出外层表的第一个元组的Sdept=’CS‘,传入到内层查询中。形成
SELECT *
FROM Student S2
WHERE S2.Sdept='CS' AND S2.Sname='刘晨';
可以查出来内容,其内层查询结果为真,表示该学生与刘晨在同一个系中,在结果表中。
(2)再取外层表中的下一个元组传入到内层查询中,依次查询。
外层表取第三个元组时将S1.Sdept='MA’传入内层表中,形成
SELECT *
FROM Student S2
WHERE S2.Sdept='MA' AND S2.Sname='刘晨';
查询结果为空,其内层查询结果为假,表示该学生与刘晨不在同一个系中,所以该学生不在结果表中。
(3)依次进行同种方法的查询,直至将外层全部元组查询完毕。
由于在外层中每次取出不同的Sdept,所以内层的S1.Sdept取值不同,当其取不同的值时,内层查询的结果如果为空,则该学生不在结果表中;如果内层查询结果不为空,则该学生在结果表中。
用EXISTS/NOT EXISTS实现全称量词(难点)
SQL语言中没有全称量词
,但是可以把带有全称量词的谓词转换成为等价的带有存在量词的谓词:
【例3.62】查询选修了全部课程的学生姓名。
SELECT Sname
FROM Student
WHERE NOT EXISTS
(SELECT *
FROM Course
WHERE NOT EXISTS
(SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno=Course.Cno));
转义后的表达:没有一门课程是他不选修的。
执行过程:相关子查询,由外到内
1、从Student中取第一个值:201215121
2、进入第二层:Course取值:1
3、进入第三层:SELECT * FROM SC WHERE Sno=‘201215121’ AND Cno=‘1’;可以查询出内容,结果为T,经过否定之后变为F
4、返回第二层,Course取值:2
5、进入第三层:SELECT * FROM SC WHERE Sno=‘201215121’ AND Cno=‘2’;可以查询出内容,结果为T,经过否定之后变为F;
6、返回第二层:Course取值:3
7、进入第三层:SELECT * FROM SC WHERE Sno=‘201215121’ AND Cno=‘3’;可以查询出内容,结果为T,经过否定之后变为F;
8、返回第二层,Course取值:4
9、进入第三层:SELECT * FROM SC WHERE Sno=‘201215121’ AND Cno=‘4’;不能查询出内容,结果为F,经过否定之后变为T;
10、……经实验,当Course取值分别为5,6时,结果都为F,否定之后都为T
11、返回第二层,Course取值为7
12、进入第三层,SELECT * FROM SC WHERE Sno=‘201215121’ AND Cno=‘7’;不能查询出内容,结果为F,经过否定之后变为T;
13、得到第二层结果
=T;
14、经过否定后,得到第一层的结果:F
15、结论:201215121没有选修全部课程
以此类推,依次取Student表中剩下的值。
如果要是想最后结果得到T,即该学生选修了全部的课程,那么第二层的结果就必须为F,则第三层中就必须全为T.
用EXISTS 或NOT EXISTS 实现逻辑蕴含(难点)
SQL语言中没有蕴含逻辑运算,但是可以利用谓词演算将逻辑蕴含谓词等价转换为:
【例3.63】查询至少选修了学生201215122选修的全部课程的学生号码。
SELECT DISTINCT Sno
FROM SC SCX
WHERE NOT EXISTS
(SELECT *
FROM SC SCY
WHERE SCY.Sno='201215122' AND NOT EXISTS
(SELECT *
FROM SC SCZ
WHERE SCZ.Sno=SCX.Sno AND SCZ.Cno=SCY.Cno)
);
解题思路:
1、用逻辑蕴涵表达:查询学号为x的学生,对所有的课程y,只要201215122学生选修了课程y,则x也选修了y。
2、形式化表示:
用P表示谓词 “学生201215122选修了课程y”
用q表示谓词 “学生x选修了课程y”
则上述查询为:
3、等价变换:
4、变换后语义:不存在这样的课程y,学生201215122选修了y,而学生x没有选。
选出一个学生的学号a
(选出‘201215122’选修课程的课序号b)
(在SC表中是否能够查询到Sno=a,Cno-b的结 果,若有则该学生选修了课序号b的课)
执行过程:相关子查询,从外到内
1、从SCX取第一个值:201215121
2、进入第二层:SCY取值:1
3、进入第三层:SELECT * FROM SC SCZ WHERE SCZ.Sno=‘201215121’ AND SCZ.Cno=‘1’;查询结果为T,经过否定之后为F
4、返回第二层:SELECT * FROM SC SCY WHERE SCY.Sno=‘201215122’ AND F;查询结果为F
5、第二层,SCY取值:2
6、进入第三层:SELECT * FROM SC SCZ WHERE SCZ.Sno=‘201215121’ AND SCZ.Cno=‘2’;查询结果为T,经过否定之后为F
7、返回第二层:SELECT * FROM SC SCY WHERE SCY.Sno=‘201215122’ AND F;查询结果为F
8、第二层,SCY取值:3
9、进入第三层:SELECT * FROM SC SCZ WHERE SCZ.Sno=‘201215121’ AND SCZ.Cno=‘3’;查询结果为T,经过否定之后为F
10、返回第二层,SELECT * FROM SC SCY WHERE SCY.Sno=‘201215122’ AND F;查询结果为F
11、得到第二层结果:FFF=F;
12、经过否定之后得到第一层结果:T
13、结论:201215121选修了学生201215122选修的全部课程
依次类推…直至SCX中全部取值完毕
当SCX取值201215123时,
第二层,SCY取值:2
进入第三层:SELECT * FROM SC SCZ WHERE SCZ.Sno=‘201215123’ AND SCZ.Cno=‘2’;查询结果为T,经过否定之后为F
返回第二层,SELECT * FROM SC SCY WHERE SCY.Sno=‘201215123’ AND F;查询结果为F
数据查询4—集合查询
集合操作的种类:(1)并-UNION (2)交-INTERSECT (3)差-EXCEPT
参加集合操作的各查询结果的列数必须相同;对应项的数据类型必须相同
【例3.64】查询计算机科学系的学生及年龄不大于19岁的学生。(或,并操作)
方法一:
SELECT *
FROM Student
WHERE Sdept='CS'
UNION
SELECT *
FROM Student
WHERE Sage<=19;
方法二:
SELECT *
FROM Student
WHERE Sdept='CS' OR Sage<=19;
UNION:将多个查询结果合并起来时,系统自动去掉重复元组
UNION ALL:将多个查询结果合并起来时,保留重复元组
【例3.65】查询选修了课程1或者选修了课程2的学生。
SELECT *
FROM SC
WHERE Cno='1'
UNION
SELECT *
FROM SC
WHERE Cno='2';
【例3.66】查询计算机科学系的学生与年龄不大于19岁的学生 的交集。
方法一:
SELECT *
FROM Student
WHERE Sdept='CS'
INTERSECT
SELECT *
FROM Student
WHERE Sage<=19;
方法二:单表查询的多重条件查询
SELECT *
FROM Student
WHERE Sdept='CS' AND Sage<=19;
【例3.67】查询既选修了课程1又选修了课程2 的学生。
方法一:
SELECT Sno
FROM SC
WHERE Cno='1'
INTERSECT
SELECT Sno
FROM SC
WHERE
Cno='2';
方法二:嵌套查询
SELECT Sno
FROM SC
WHERE Cno='1' AND Sno IN
(SELECT Sno
FROM SC
WHERE Cno='2')
【例3.68】查询计算机科学系的学生与年龄不大于19岁的学生的差集。(即查询计算机系中除去年龄不大于19岁的学生)
方法一:
SELECT *
FROM Student
WHERE Sdept='CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage<=19;
方法二:实际上是查询计算机科学系中年龄大于19岁的学生。使用单表查询
SELECT *
FROM Student
WHERE Sdept='CS' AND Sage>19;
数据查询5—基于派生表的查询
子查询不仅可以出现在WHERE子句中,还可以出现在FROM子句中,这时子查询生成的临时派生表成为主查询的查询对象。
改写【例3.57】找出每个学生超过他自己选修课程平均成绩的课程号.
方法一:相关查询,使用聚集函数
SELECT Sno, Cno
FROM SC x
WHERE Grade >=( SELECT AVG(Grade)
FROM SC y
WHERE y.Sno=x.Sno);
方法二:基于派生表的查询
SELECT Sno,Cno
FROM SC,(SELECT Sno,AVG(Grade)
FROM SC
GROUP BY Sno)
AS AVG_sc(avg_sno,avg_grade)
WHERE SC.Sno=AVG_SC.avg_sno AND SC.Grade>=AVG_SC.avg_grade;
方法一更为简单易于理解,在执行效率上也更高,方法二相对来说不易理解,执行效率相对较低。
如果子查询中没有聚集函数,派生表可以不指定属性列,子查询SELECT子句后面的列名为其缺省属性。
改写【例3.60】查询所有选修了1号课程的学生姓名。
方法一:连接查询
SELECT Sname
FROM Student,SC
WHERE Student.Sno=SC.Sno AND Cno='1';
方法二:基于派生表的查询
SELECT Sname
FROM Student,(SELECT Sno FROM SC WHERE Cno='1') AS SC1
WHERE Student.Sno=SC1.Sno;
在表SC1中的列属性为Sno,先做了查询操作,再做连接操作,相对于方法二,方法一直接进行连接选出符号条件的结果更为简单,易于理解。
SELECT 查询的一般形式<>/font
SELECT [ALL|DISTINCT] <目标表达式>[别名][,<目标表达式>[别名]]…
FROM <表名或视图名>[别名]
[,<表名或视图名>[别名]]…
|(<SELECT 语句>)[AS]<别名>
[WHERE <条件表达式>]
[GROUP BY<列名1>[HAVING<条件表达式>]]
[ORDER BY<列名2>[ASC|DESC]];