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中存不存在这个Sno值,也就是看这个同学有没有选课,如果存在并且其Cno= ‘1’,则不为空,取此Student.Sname送入结果表
[例 3.61] 查询没有选修1号课程的学生姓名。
SELECT Sname
FROM Student
WHERE NOT EXISTS(
SELECT *
FROM SC
WHERE Sno = Student.Sno AND Cno='1');
NOT EXISTS就是取反操作
所有带IN谓词、比较运算符、ANY和ALL谓词的子查询都能用带EXISTS谓词的子查询等价替换:
[例 3.55]查询与“刘晨”在同一个系学习的学生。
可以用带EXISTS谓词的子查询替换:
SELECT Sno,Sname,Sdept
FROM Student S1
WHERE EXISTS(
SELECT *
FROM Student S2
WHERE S2.Sdept = S1.Sdept AND S2.Sname = '刘晨');
按顺序取Student中的Sdept值,看这个Sdept值跟刘晨的Sdept值是否相等。相等就是存在,则不为空,取此学生的Sno,Sname,Sdept送入结果表。
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) );
转义后的表达:没有一门课程是他不选修的
过程:
进入第一层:取Student中的第一个Sno值201215121
进入第二层:取Course中的第一个Cno值1
进入第三层:看SC中是否存在Sno=201215121且Cno=1的(Sno= Student.Sno AND Cno= Course.Cno),存在为F,不存在为T(注意是NOT EXISTS取反),这里为F
返回第二层:取Course中的第二个Cno值2
进入第三层:看SC中是否存在Sno=201215121且Cno=2的(Sno= Student.Sno AND Cno= Course.Cno),存在为F,不存在为T,这里为F
……
返回第二层:取Course中的最后一个Cno值7
进入第三层:看SC中是否存在Sno=201215121且Cno=7的(Sno= Student.Sno AND Cno= Course.Cno),存在为F,不存在为T,这里为T
得到第二层结果:F
F
F
T
T
T
T=T
得到第一层结果:F(取反)
所以Sno值为201215121的第一个学生没有选修所有课
进入第一层:取Student中的第二个Sno值201215122
……
以此类推
这里虽然有些麻烦但其实是很好理解的
SQL语言中没有蕴涵逻辑运算
可以利用谓词演算将逻辑蕴涵谓词等价转换为:
[例 3.63]查询至少选修了学生201215122选修的全部课程的学生号码。
用逻辑蕴涵表达:
查询学号为x的学生,对所有的课程y,只要201215122学生选修了课程y,则x也选修了y。
形式化表示:
用P表示谓词 “学生201215122选修了课程y”
用q表示谓词 “学生x选修了课程y”
则上述查询为:
等价变换:
变换后语义:不存在这样的课程y,学生201215122选修了y,而学生x没有选
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));
过程:
进入第一层:取SCX中的第一个Sno值201215121
进入第二层:取SCY中的第一个Cno值1
进入第三层:看SCZ中是否存在Sno=201215121且Cno=1的(SCZ.Sno=SCX.Sno AND SCZ.Cno=SCY.Cno),存在为F,不存在为T,这里为F
返回第二层:看SCY中是否存在Sno=201215121且F(WHERE SCY.Sno = ‘201215122’ AND F[这里的F是上一步的结果]),存在为F,不存在为T,这里为F
取SCY中的第二个Cno值2
进入第三层:看SCZ中是否存在Sno=201215121且Cno=2的(SCZ.Sno=SCX.Sno AND SCZ.Cno=SCY.Cno),存在为F,不存在为T,这里为F
返回第二层:看SCY中是否存在Sno=201215121且F(WHERE SCY.Sno = ‘201215122’ AND F),存在为F,不存在为T,这里为F
取SCY中的第三个Cno值3
进入第三层:看SCZ中是否存在Sno=201215121且Cno=3的(SCZ.Sno=SCX.Sno AND SCZ.Cno=SCY.Cno),存在为F,不存在为T,这里为F
返回第二层:看SCY中是否存在Sno=201215121且F(WHERE SCY.Sno = ‘201215122’ AND F),存在为F,不存在为T,这里为F
取SCY中的第四个Cno值5
进入第三层:看SCZ中是否存在Sno=201215121且Cno=5的(SCZ.Sno=SCX.Sno AND SCZ.Cno=SCY.Cno),存在为F,不存在为T,这里为T
得到第二层结果:F
F
F
F
T=T
得到第一层结果:F
所以Sno值为201215121的第一个学生没有全部选修学生201215122选修的全部课程
进入第一层:使用了关键词DISTINCT,跳过重复的,取Student中的下一个Sno值201215122
……
以此类推
集合操作的种类:
并-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岁的学生的交集。
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';
也可以表示为嵌套查询:
SELECT Sno
FROM SC
WHERE Cno='1' AND Sno IN(
SELECT Sno
FROM SC
WHERE Cno='2');
[例 3.68] 查询计算机科学系的学生与年龄不大于19岁的学生的差集。
SELECT *
FROM Student
WHERE Sdept='CS'
EXCEPT
SELECT *
FROM Student
WHERE Sage <=19;
实际上是查询计算机科学系中年龄大于19岁的学生
SELECT *
FROM Student
WHERE Sdept= 'CS' AND 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
其中嵌套的查询结果为一张临时派生表,可以看为一张实际的表。将该表起名为Avg_sc,Sno,Avg(Grade)的列名分别为avg_sno,avg_grade
如果子查询中没有聚集函数,
派生表可以不指定属性列,
子查询SELECT子句后面的列名为其缺省属性。
[例]查询所有选修了1号课程的学生姓名,可以用如下查询完成:
SELECT Sname
FROM Student,(SELECT Sno FROM SC WHERE Cno='1')
AS SC1
WHERE Student.Sno=SC1.Sno;
先进行查询,查询结果为一张临时派生表,再将两表连接
与之前的方法对比,之前是先进行连接再进行查询,这种方法是先进行查询,将符合条件的内容构成一张临时派生表,再进行连接。感觉这种方法会更快捷,高效
SELECT语句的一般格式
SELECT [ALL|DISTINCT]<目标列表达式> [别名] [ ,<目标列表达式> [别名]] …
FROM <表名或视图名> [别名]
[,<表名或视图名> [别名]] …
|(<SELECT语句>)[AS]<别名>
[WHERE <条件表达式>]
[GROUP BY <列名1>[HAVING<条件表达式>]]
[ORDER BY <列名2> [ASC|DESC]];
DISTINCT:去除重复值;
自身连接时要用到别名;
FROM中可有子查询,用AS生成一张临时派生表;
WHERE中的条件表达式是最复杂的部分,单表查询、连接查询、嵌套查询、集合查询、基于此派生表的查询。其中嵌套查询又最为复杂;
分组要用GROUP BY,HAVING+条件;
排序用ORDER BY,ASC为升序,DESC为降序,缺省为ASC。
总结:最后看着SELECT语句的一般格式就能回想起这一阵所学的内容。确实今天学习的嵌套查询EXISTS是最难理解的,但是老师说难也不能怂,趁热把这些东西又仔细的看了看,保证自己理解通透,发现它一层一层的还是很有规律的,反正比物理啥的简单多了