-带有EXISTS谓词的子查询
-集合查询
-基于派生表的查询
-Select总结
下午上次课学到了嵌套查询-3
今天我们来看一下
带有EXISTS谓词的子查询
3/20 15:30
EXISTS谓词:
存在量词 ∃
带有EXISTS谓词的子查询不返回任何数据,只产生逻辑真值“true”或逻辑假值“false”。
若内层查询结果非空,则外层的WHERE子句返回真值
若内层查询结果为空,则外层的WHERE子句返回假值
由EXISTS引出的子查询,其目标列表达式通常都用 * ,因为带EXISTS的子查询只返回真值或假值,给出列名无实际意义。
NOT EXISTS谓词:
若内层查询结果非空,则外层的WHERE子句返回假值
若内层查询结果为空,则外层的WHERE子句返回真值
[例 3.60]查询所有选修了1号课程的学生姓名。
SELECT Sname
FROM Student
WHERE EXISTS(
SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno= '1');
执行的顺序是先从Student中取一个Sno,若在SC中并且Cno=1,则取该元组的Sname送入结果表
由EXISTS引出的子查询,其目标表达式都用 *,如图
[例 3.61] 查询没有选修1号课程的学生姓名。
SELECT Sname
FROM Student
WHERE NOT EXISTS(
SELECT *
FROM SC
WHERE Sno = Student.Sno AND Cno='1');
这一题和【例3.60】对比就好理解得多了
只需将EXISTS换成NOT EXISTS即可
【例3.60】中若外层WHERE语句返回真值,则【例3.61】外层WHERE语句返回假值,也就是说把选修了1号课程的学生过滤掉,剩下的全是没有选修一号课程的学生
看一下结果
所有带IN谓词、比较运算符、ANY和ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换:
可见,EXISTS谓词的强大,能代替,但不一定用它最方便。但是EXISTS谓词查询不能全被其他的代替。两者不完全等价
[例 3.55]查询与“刘晨”在同一个系学习的学生。
同一个题存在上一个作业中【例3.55】中
SELECT Sno,Sname,Sdept
FROM Student S1
WHERE EXISTS(
SELECT *
FROM Student S2
WHERE S2.Sdept = S1.Sdept AND S2.Sname = '刘晨');
先是子查询,依次取Sdept若与名字为刘晨的Sdept相等,则返回真值,
取该学生的Sno,Sname,Sdept送入结果表中
SQL语言中没有全称量词∀(FOR ALL),可以把带有全称量词的谓词转换为等价的带有存在量词的谓词:
(∀x)P≡¬(∃x(¬P))
[例 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中的第一个Sno值
2. 取Course中的第一个Cno值
3. 看SC中是否存在1中取得第一个Sno值且Cno=第一个Cno的,存在为F,不存在为T,这里因为是NOT EXISTS
4. back->2,取Course中的第二个Cno值
5. 看SC中是否存在Cno=第二个Cno值的(这里因为是同一个人所以不再确认Sno),存在为F,不存在为T,这里为F
6. 再back->2,再取Course的第三个Cno值,再看SC中........
7. 最终得到第二层结果(假设):T∨T∨T∨T∨T∨T∨T=T**(注意这是第二层的结果 并不是最终结果)*
8. 得到第一层结果:F(取反)
9. 所以Sno值为第一个值的第一个学生没有选修所有课
然后进入第一层:取Student中的第二个Sno值依次执行上述操作
直至Sno全部循环一遍 ,这个就是循环吼吼
SQL语言中没有蕴涵逻辑运算
可以利用谓词演算将逻辑蕴涵谓词等价转换为:
p→q≡¬p∨q
[例 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”
③:则上述查询为: (∀y)p→q
3.等价变换: (∀y)p→q≡¬∃y(p∧q)
4.变换后语义:不存在这样的课程y,学生201215122选修了y,而学生x没有选
执行过程
从外向内
1.取SCX中第一个201215121
2.第二层从SCY中取1
3.第三层(WHERE SCZ.Sno=201215121 AND SCZ.Cno=1)取值为T
4.back to 第二层(SCY.Sno=‘201215122’ AND F) 取值为F
5.第二层从SCY中取2
6.第三层:SCZ.Sno=201215121 AND SCZ.Cno=2 结果为T
再回到第二层.....
最终得到第二层结果 F
得到第一层结果 T
结论:201215121选修了201215122选修的全部课程
这里,DIDTINCT起的作用是取回唯一,不会重复。下面的以此类推
集合查询
集合操作的种类
并-UNION
交-INTERSECT
差-EXCEPT
参加集合操作的各查询结果的列数必须相同
对应项的数据类型必须相同
UNION:将多个查询结果合并起来时,系统自动去掉重复元组
UNION ALL:将多个查询结果合并起来时,保留重复元组
【例3.64】查询计算机科学系的学生及年龄不大于19岁的学生。
SELECT *
FROM Student
WHERE Sdept= 'CS'
UNION
SELECT *
FROM Student
WHERE Sage<=19;
【例3.65】查询选修了课程1或者选修了课程2的学生。
SELECT Sno
FROM SC
WHERE Cno='1'
UNION
SELECT Sno
FROM SC
WHERE Cno= '2';
【例3.66】查询计算机科学系的学生与年龄不大于19岁的学生的交集。
也就是找计算机科学系中小于等于19岁的学生
简单的话可以直接在Student中查询
SELECT *
FROM Student
WHERE Sdept= 'CS' AND Sage<=19;
按集合查询
SELECT *
FROM Student
WHERE Sdept='CS'
INTERSECT
SELECT *
FROM Student
WHERE Sage<=19
--=查询计算机科学系中年龄不大于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';
[例 3.68] 查询计算机科学系的学生与年龄不大于19岁的学生的差集。
这道题和【3.66】相比 这个题就是相反的呀
只要求计算机科学系中大于19岁的学生就好了
SELECT *
FROM Student
WHERE Sdept= 'CS' AND Sage>19;
集合查询
SELECT *
FROM Student
WHERE Sdept='CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage <=19;
基于派生表的查询
子查询不仅可以出现在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_sc.avg_sno AND SC.Grade >=Avg_sc.avg_grade
看着好乱啊,我们来分析一一下
这里我们看到AS后 这里是一个表,名为Avg_sc,avg_sno,avg_grade分别是它的两个列名,实质为Sno,Avg(Grade)。
Avg_sc是一个临时派生表。
也可以这样写
SELECT Sno, Cno /*相关子查询*/
FROM SC x
WHERE Grade>=(SELECT AVG(Grade)
FROM SC y
WHERE y.Sno=x.Sno);
如果子查询中没有聚集函数,
派生表可以不指定属性列,
子查询SELECT子句后面的列名为其缺省属性。
【例3.60】查询所有选修了1号课程的学生姓名。
可以由以下三种完成
一:
SELECT Sname
FROM Student,SC
WHERE SC.Sno=Student.Sno AND Cno='1';
这个最简单也容易理解,就是只要SC中的学生Cno有为‘1’的值,那就符合条件
二:
SELECT Sname
FROM Student
WHERE EXISTS(SELECT *
FROM SC
WHERE Sno=Student.Sno AND Cno='1');
第二个利用了EXISTS谓词 若存在返回真值,该学生的Sname放入结果表
三:
SELECT Sname
FROM Student,(SELECT Sno FROM SC WHERE Cno='1') AS SC1
WHERE Student.Sno=SC1.Sno;
第三个就是从SC 中选择Cno=‘1’的学号作为一个临时表SC1,然后将Sno=SC1.Sno作为条件,将符合条件的Sname放入结果表
3/21 11:37
SELECT总结
这一块每次写语句少不了的那肯定是SELECT,接下来我们来一起回顾一下数据查询这一块的知识吧!
SELECT [ALL|DISTINCT]
<目标列表达式> [别名] [ ,<目标列表达式> [别名]]…
FROM <表名或视图名> [别名]
[ ,<表名或视图名> [别名]]…
|(<SELECT语句>)[AS]<别名>
[WHERE <条件表达式>]
[GROUP BY <列名1>[HAVING<条件表达式>]]
[ORDER BY <列名2> [ASC|DESC]];
如果直接回顾这一块的知识,最开始想起来的就是单表查询,然后里面有
选择表中若干列,
选择表中若干元祖,
然后就是连接查询,我想到的是等值连接和自然连接,记得自然连接是特殊的等值连接,去掉重复列,然后就有自身连接外连接,复杂一点就是多表连接
嵌套查询记得子查询和父查询之间有相关子查询和不相关子查询,还有各种子查询(带有IN谓词,带有比较运算符,ANY,SOME,ALL,带有EXISTS谓词…)
集合查询
并(UNION)交(INTERSECT)叉(EXPECT)
基于派生表的查询
这周还好,时间还算富裕,提前做了,本来都是赶到周日的时候下午的时候才开始做,今天周六就做完了,点个赞。