在SQL语言中,一个select-from-where语句称为一个查询块,将一个查询块嵌套在另一个查询块的where子句或having短语的条件中的查询称为嵌套查询上层的查询块称为外层查询或父查询,下层查询块称为内层查询或子查询。
PS:子查询的select语句中不能使用order by子句,order by子句只能对最终查询结果排序。
例1:查询与“刘晨”在同一个系学习的学生。
可以先分布来完成此查询,然后再构造嵌套查询
①确定“刘晨”所在系名,结果为CS
select Sdept
from Student
where Sname='刘晨';
②查找所有在CS系学习的学生
select Sno,Sname,Sdept
from Student
where Sdept='CS';
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='刘晨';
例2:查询选修了课程名为“信息系统”的学生学号和姓名
解题方法:本查询涉及学号、姓名和课程名三个属性,学号和姓名存放在Student表中,课程名存放在Course表中,两个表并无直接联系,必须通过SC表建立,所以一共涉及三张表。
select Sno,Sname -- ③最后再Student关系中取出Sno和Sname
from Student
where Sno in
(select Sno -- ②然后在SC关系中找出选修了3号课程的学生学号
from SC
where Cno in
(select Cno -- ①首先在Course关系中找出信息系统的课程号,结果为3
from Course
where Cname='信息系统'
)
);
也可以使用连接查询,对应SQL语句如下
select Student.Sno,Sname
from Student,SC,Course
where Student.Sno=SC.Sno and
SC.Cno=Course.Cno and
Course.Cname='信息系统';
二者查询结果一致,如下
上述的两个例子都是不相关子查询的,下面是相关子查询的,即子查询的查询条件依赖于父查询,整个查询语句称为相关嵌套查询
该类查询是指父查询与子查询之间用比较运算符进行连接,当用户确切知道内层查询返回的是单个值时,就可以使用> < >=等这类的比较运算符。
应用:上述例1中由于一个学生只可能在一个系学习,即子查询的结果是一个值,所以可以用=代替in
select Sno,Sname,Sdept
from Student
where Sdept=
(select Sdept
from Student
where Sname='刘晨'
);
例:找出每个学生超过他自己选修课程平均成绩的课程号
内层查询是求每个学生所有选修课程的平均成绩,至于是哪个学生的平均成绩要看参数x.Sno的值,即子查询是依赖于父查询的,所以为相关子查询
select Sno,Cno
from SC x
where Grade>=(select avg(Grade)
from SC y
where y.Sno=x.Sno);
为了便于自己的理解,下面将书上的关于这一例子(实际上是它的内部执行原理)写上。
该语句的一种可能的执行过程采用以下三个步骤:
①从外层查询中取出SC的一个元组x,将元组x的Sno值(201215121)传送给内层查询
select avg(Grade)
from SC y
where y.Sno='201215121';
②执行内层查询,得到88(近似值),用该值代替内层查询,得到外层查询
select Sno,Cno
from SC x
where Grade>=88;
③执行这个查询,得到结果
201215121 1
201215121 3
然后外层查询取出下一个元组重复作上述①-③步骤的处理,直到外层的SC元组全部处理完毕。
子查询返回单值时可以用比较运算符,但返回多值时要使用any(或some,视系统定)或all谓词修饰。any是某个值,而all是所有值
例1:查询非计算机科学系中比计算机科学系任意一个学生年龄小的学生姓名和年龄(随便一个都可以,所以是any)
select Sname,Sage
from Student
where Sage<any(select Sage
from Student
where Sdept='CS')
and Sdept!='CS'; -- 也可将!=改为<>
变式:由于是小于随便一个的年龄即可,那么只要小于计算机科学系的最大年龄即可
select Sname,Sage
from Student
where Sage<(select max(Sage)
from Student
where Sdept='CS')
and Sdept!='CS'; -- 也可将!=改为<>
例2:查询非计算机科学系中比计算机科学系所有学生年龄都小的学生姓名及年龄(所有,使用all)
select Sname,Sage
from Student
where Sage<all(select Sage
from Student
where Sdept='CS')
and Sdept!='CS'; -- 也可将!=改为<>
同理,也可是更改,由于是小于所有的,则需要小于最小的,使用聚集函数min即可。
exists代表存在量词,带有exists谓词的子查询不返回任何数据,只产生逻辑真值true或false。
例1:查询所有选修了1号课程的学生姓名
select Sname
from Student
where exists
(select *
from SC
where Sno=Student.Sno and Cno='1');
使用exists后,若内层查询结果非空,则外层的where子句返回真值,否则返回返回假值,由exists引出的子查询,其目标列表达式通常都用*,给出实际列名无实际意义。
例2:查询没有选修1号课程的学生姓名
可以使用not exists
select Sname
from Student
where not exists
(select *
from SC
where Sno=Student.Sno and Cno='1');
PS:所有带in谓词,比较运算符,any和all谓词的子查询都能用带exists的等价替换
例3:查询选修了全部课程的学生姓名
由于没有全称量词,所以可以将题目转换为:查询这样的学生,没有一门课程是他不选修的。
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:查询至少选修了学生201215122选修的全部课程的学生号码
可是用逻辑蕴涵来表达:查询学号为x的学生,对所有的课程y,只要201215122学生选修了课程y,则x也选修了y。但是SQL中没有蕴涵,可以对其进行转换,为:不存在这样的学生,学生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));