嵌套查询
带有EXISTS谓词的子查询
EXISTS谓词 —— 存在量词
带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”
- 若内层查询为空,外层WHERE子句返回false
- 若内层查询非空,外层WHERE子句返回true
因为带有EXISTS谓词的子查询不返回任何数据,指定列名没有意义,所以目标列表达式全用*代替。
NOT EXISTS(与EXISTS正好相反)
- 若内层查询非空,外层WHERE子句返回false
- 若内层查询为空,外层WHERE子句返回true
【例3.60】查询所有选修了1号课程的学生姓名
选修一号课程 —— SC表
学生姓名 —— Student表
SELECT Sname
FROM Student
WHERE EXISTS
(SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno='1');
这是个相关子查询。
执行过程:
①在外层Student中取出一个元组的Sno
②进入内层EXISTS判断WHERE子句是否成立,即SC表中是否存在这样的元组
③存在,返true;为空,返false
④若外层的WHERE子句为真,就取出该元组的Sname
⑤重复上述步骤,直到将Student中的元组取完
【例3.61】查询没有选修1号课程的学生姓名
这个跟上题正好相反,用NOT EXISTS
SELECT Sname
FROM Student
WHERE NOT EXISTS
(SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno='1');
不同形式之间的查询可以替换
- 一些带有EXISTS和NOT EXISTS谓语的子查询不能被其他形式的子查询所替换
- 所有带IN谓词、比较运算符、ANY或ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换
EXISTS好万能。。。果然要学到最好的总是要付出一些代价,要征服EXISTS!!!
【例3.55】查询与“刘晨”在同系的学生
之前使用的是IN谓词或者 =
用EXISTS等价替换:
SELECT Sno,Sname,Sdept
FROM Student S1
WHERE EXISTS
(SELECT *
FROM Student S2
WHERE S1.Sdept=S2.Sdept AND S2.Sname='刘晨');
依次取S1表中元组的Sdept,看是否和S2中刘晨的Sdept相等,相等就是满足条件,内层查询结果就不是空,外层WHERE子句就是真,就取该元组的Sno,Sname,Sdept。
!!!用EXISTS和NOT EXISTS实现全称量词
SQL中没有全称量词,所以利用离散数学中的逻辑转化,将全称量词 用存在量词 表示。
双重否定!
可以先将自然语言做转换,再用量词表示。
【例3.62】查询选修了全部课程的学生姓名。
选修了全部课程的学生 没有课程是该学生没有选修的
SELECT Sname
FROM Student
WHERE NOT EXISTS
(SELECT *
FROM Course
WHERE NOT EXISTS
(SELECT *
FROM SC
WHERE SC.Cno = Course.Cno
AND Student.Sno=SC.Sno)
);
双重否定 —— NOT EXISTS嵌套
这里修改了一下表,让结果有数据“李勇”。
执行过程
进入第一层:取Student表中的一个Sno值‘201215121’
进入第二层:取Course表中的一个Cno值‘1’
进入第三层:判断SC表中是否有 Sno=‘201215121’ 并且Cno=‘1’这样的元组,经过NOT EXISTS的取反,存在F,不存在就是T—— 结果F
返回第二层:取第二个Cno值‘2’
进入第三层:判断是否存在 Sno=‘201215121’ 并且Cno=‘2’这样的元组,结果取反 —— 结果F
…
返回第二层:取最后一个Cno值‘7’
进入第三层:判断是否存在 Sno=‘201215121’ 并且Cno=‘7’这样的元组,结果取反 —— 结果F
得到第二层的结果是:F
F
F…
F = F
返回第一层的结果:T(NOT EXISTS再次取反)
结果为T,所以将Sno='201215121’学生的学生姓名取出放入结果表中。
进入第一层:取第二个Sno值‘201215122’
再次重复以上的操作。
刚开始这里我一直搞不懂为什么第二层的结果都是逻辑或
的结果,后来慢慢地想通了。
逻辑或 ,表示只要有一个为真就是真,也就是只要表中有一个满足条件的也就是存在,表非空,那就是真。
!!!用EXISTS和NOT EXISTS实现逻辑蕴涵
SQL中没有逻辑蕴涵运算,也是可以用谓词演算等价转换:
蕴涵的符号是一个箭头 ,就是可以推出的意思,如果p,那么q;强调一种包含关系。
【例3.63】查询至少选修了学生201215122选修的全部课程的学生学号。
所要查询的学生选修的课程包含了201215122学生所选的课程,用蕴涵。
蕴涵表达:查询学号为x的学生,对所有课程y,只要201215122选修了y,则x也选了y。
形式化表示:
p:学生201215122选修了课程y
q:学生x选修了课程y
上述查询就是:
用上述式子等价转换:
其中运用了德摩根律
转义后的表达:
不存在一门课程y,201215122选了,但是x没有选
用NOT EXISTS
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 Sno = SCX.Sno
AND Cno = SCY.Cno)
);
上题中将201215121设置成了选修了所有课程,所以结果中有。
执行过程:
进入第一层:在SCX表中选取一个Sno值(‘201215001’)
进入第二层:在SCY中取第一个Cno值‘2’
(我的表中第一个Cno为2)
进入第三层:判断是否有Sno='201215001’和Cno='2’这样的元组,NOT EXISTS取反,存在为F,不存在为T——结果F
相当于第三层内的语句为:
SELECT *
FROM SC SCZ
WHERE Sno = '201215001' AND Cno = '2';
不是空,所以是T,NOT EXISTS取反后是F
返回第二层:是否有Sno = ‘201215122’ AND F(逻辑与操作),结果是空,为F,再次取第二个Cno的值‘3’
进入第三层:
相当于
SELECT *
FROM SC SCZ
WHERE Sno = '201215001' AND Cno = '3';
存在这样的元组,NOT EXISTS取反,结果为F
返回第二层:此时WHERE子句后为
Sno=‘201215122’ AND F —— F
得到第二层的结果
得到第一层结果:T
所以201215001学生满足条件
进入第一层:使用DISTINCT,跳过相同的值,取下一个Sno,201215121
…
重复上述操作
刚开始忘记了写SCY.Sno = ‘201215122’的条件,结果里只有‘201215121’这一个值。不加学号的条件的话,就是选修了所有SC表中出现的课程的学生学号。
这里可以简单理解为
SCY是先经过Sno = ‘201215122’这个条件的选择后再选Cno的值。就像是先产生一个中间关系,在与内层中的SCZ做连接。
(不知道这样理解对不对,但是感觉这样子会好理解很多)
集合查询
并——UNION
交——INTERSECT
差——EXCEPT
前提:
- 参加集合操作的各查询结果列数必须相同
- 对应项的数据类型必须相同
【例3.64】查询CS系中学生及年龄不大于19岁的学生
及 —— 并
SELECT *
FROM Student
WHERE Sdept = 'CS'
UNION
SELECT *
FROM Student
WHERE Sage <= 19;
就是表中除了非CS系中大于19岁的学生,其余都包括。
刚开始,写成了下面这个样子,是错误的,UNION不是谓词,是用来连接两个查询块的!!!
SELECT *
FROM Student
WHERE Sdept = 'CS'
UNION Sage <= 19;
--错误的!!!
UNION:将多个查询结果合并起来时,系统自动去掉重复元组
UNION ALL:将多个查询结果合并起来时,保留重复元组
(相当于在UNION这缺省值变为 DISTINCT了)
【例3.65】查询选修了1号课程或者2号课程的学生。
SELECT Sno
FROM SC
WHERE Cno = '1'
UNION
SELECT Sno
FROM SC
WHERE Cno='2';
【例3.66】查询CS系与年龄不大于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】查询CS系学生与年龄不大于19岁学生的差集
SELECT *
FROM Student
WHERE Sdept = 'CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage <= 19;
就是 CS 系中大于19岁的人
等价于
SELECT *
FROM Student
WHERE Sage > 19 AND Sdept = 'CS';
基于派生表的查询
子查询不仅可以出现在WHERE子句中,还可以出现在FROM子句中。子查询生成的临时派生表成为主查询的查询对象。
【例3.57】找出每个学生超出他选修课程平均成绩的课程号
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_sno AND SC.Grade >= avg_grade;
注意括号的位置!!!子查询块用括号括起来,不包括AS语句。
如果子查询中没有聚集函数,派生表可以不指定属性列,子查询SELECT子句后面的列名为其缺省属性。
因为有聚集函数的列是没有列名的,要指定。没有聚集函数,就取其原来的名字。
【例】查询所有选修了1号课程的学生姓名
SELECT Sname
FROM Student,(SELECT Sno FROM SC WHERE Cno='1') AS SC1
WHERE Student.Sno = SC1.Sno;
SC1缺省后面的属性列,对应列名仍为Sno
SELECT语句的一般形式
SELECT [ALL|DISTINCT]
<目标列表达式> [别名][,<目标列表达式> [别名]]...
FROM <表名或视图名> [别名]
[,<表名或视图名> [别名]]...
|(<SELECT语句>)[AS <别名>]
[WHERE<条件表达式>]
[GROUP BY <列名1>[HAVING <条件表达式>]]
[ORDER BY <列名2>[ASC|DESC]];
其中的(<SELECT语句>)[AS <别名>]就是基于派生表的查询。
不知不觉学了好多呀~
感觉EXISTS还是有些难度,通过分析可以理解例题,但是如果只给出例题,还是不知道如何写出答案那样子的语句,还是需要多练,多分析。