老规矩,先写总结:
- 多表连接中注意事项和小结:
- 多表连接时候,当属性列在查询的多个表里面是唯一的就可以省略表名前缀,否则必须加上表名前缀;
- 一张表进行自身连接时,可以给表起不同的名称来加以区分;
- 使用外连接,可以使连接操作中的表中含有但是不满足连接条件的也可以输出NULL值。
- 左外连接列出左边的关系,右外连接列出右边关系中所有的元组(见例 [ 3.53 ] );点我跳转
- 关系数据库管理系统在执行多表连接时,通常是先进行两个表的连接操作,再将其连接结果与第三个表进行连接;
- 在SQL语言中,一个select – from – where语句称为一个査询块。将一个査询块嵌套在另一个査询块的 where子句 或 having短语 的条件中的査询称为嵌套査询
- 上层的査询块称为外 层査询或父查询,下层査询块称为内层査询或子査询。
- 一个子査询中还可以嵌套其他子査询。但是子査询的SELECT语句中不能使用order by子句,order by子句只能对最终 査询结果排序。
- 子查询的查询条件不依赖于父查询,称为不相关子查询(见 [ 例3.55 ] )点我跳转
- 不相关子查询的求解方法:一般是先执行子查询,后执行父查询;
- 子查询的条件依赖于父查询,称为相关子查询 (见 [ 例3.57 ])点我跳转
- 有些嵌套查询可以用连接运算替代,能够用连接运算表达的査询尽可能采用连接运算。
相关子查询的求解方法 | 点我跳转 |
---|---|
谓词any 、all使用说明 | 点我跳转 |
聚集函数和 any 、all 的对应用法 | 点我跳转 |
Course表如下:
Student表如下:
SC表如下:
[例3.49] 查询每个学生及其选修课程的情况。
学生信息存储在Student表中,学生选课情况存放在SC表中,所以本査询实际上涉及Student与SC两个表。这两个表之间的联系是通过公共属性Sno实现的。
代码如下:
select Student.* , SC.*
from Student , SC
where Student.Sno = SC.Sno;
结果如下图:
[例3.50] 对【例3.49】用自然连接完成。
代码如下:
select Student.Sno , Sname , Ssex , Sage , Sdept , Cno , Grade
from Student , SC
where Student.SNo = SC.Sno;
结果如下图:
当属性列在查询的所有表里面是唯一的就可以省略表名前缀,否则必须加上表名前缀。
本例中,由于 Sname, Ssex, Sage, Sdept,Cno 和 Grade 属性列在 Student 表与 SC 表中是唯一的,因此引用时可以去掉表名前缀;而Sno在两个表都出现了,因此引用时须加上表名前綴。
[例3.51] 査询选修2号课程且成绩在90分(包含90分)以上的所有学生的学号和姓名。
代码如下:
select Student.Sno , Sname
from Student , SC
where Student.Sno = SC.Sno
and SC.Cno = '2'
and SC.Grade >= 90;
结果如下图:
该査询的一种优化(高效)的执行过程是,先从SC中挑选出Cno=2并且Grade>=90的元组形成一个中间关系,再和Student中满足连接条件的元组进行连接得到最终的结果关系。
优化查询语句代码如下:
select Student.Sno , Sname
from Student
where Student.Sno =
(
select SC.Sno from SC
where SC.Cno = '2'
and SC.Grade >= 90
);
结果如下:
我们可以看到,和原方法查询结果一致。
[例3.52] 査询每一门课的间接先修课(即先修课的先修课).
在Course表中只有每门课的直接先修课信息,而没有先修课的先修课。要得到这个信息,必须先对一门课找到其先修课,再按此先修课的课程号査找它的先修课程。这就要将Course表与其自身连接。
代码如下:
select first.Cno , second.Cpno
from Course first , Course second
where first.Cpno = second.Cno;
在此,我们为Course表取了两个别名(first 和 second);
结果如下图:
[例 3.53] 在我们常用的连接操作中,满足条件的元组才可以作为结果进行输出,不满足条件的就会被舍弃。如果我们想让元组不被舍弃,并且输出为NULL值。我们就要用到外连接。
接下来对例【3.49】修改,输出被舍弃的和没被舍弃的结果:
代码如下:
select Student.Sno , Sname , Ssex , Sage , Sdept , Cno , Grade
from Student left outer join SC
on(Student.Sno = SC.Sno);
结果如下图:
例【3.49】输出结果我搬到下面:
我们可以看到,被舍弃的元组又被输出了出来。
我们要是想去除重复的值可以用修改为以下语句:
from Student left outer join SC using(Student.Sno = SC.Sno);
左外连接列出左边的关系(本例题),右外连接列出右边关系中所有的元组,右连接运行结果如图:
[例3.54] 査询每个学生的学号、姓名、选修的课程名及成绩。
此次查询涉及了三张表,代码如下:
select Student.Sno , Sname , Cname , Grade
from Student , SC , Course
where Student.Sno = SC.Sno
and SC.Cno = Course.Cno;
结果如下图:
关系数据库管理系统在执行多表连接时,通常是先进行两个表的连接操作,再将其连接结果与第三个表进行连接。
[例3.55] 査询与“刘晨”在同一个系学习的学生。
在这里我们用到嵌套查询,什么是嵌套查询呢?
在SQL语言中,一个select – from – where语句称为一个査询块。将一个査询块嵌套在另一个査询块的 where子句 或 having短语 的条件中的査询称为嵌套査询 ,此题目中,我们先分步来完成此査询,然后再构造嵌套査询:
- 先确定“刘晨”所在系名,代码如下:
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语句中不能使用order by子句,order by子句只能对最终
査询结果排序。
显然,嵌套查询可以说是一步步地简化复杂的查询。
本例题中,子查询的查询条件不依赖于父查询,称为不相关子查询;
求解方法一般是先执行子查询,后执行父查询。
[例3.56] 查询选修了课程名为“信息系统”的学生学号和姓名。
本查询涉及三个关系,我们用两种方法实现,第一种方法代码如下:
select Sno , Sname
from Student
where Sno in
(
select Sno
from SC
where Cno in
(
select Cno
from course
where Cname = '信息系统'
)
);
结果如下图:
- 这里我们同样可以用第二种方法(连接查询)实现,代码如下:
select Student.Sno , Sname
from Student , SC , Course
where Student.Sno = SC.Sno
and SC.Cno = Course.Cno
and Course.Cname = '信息系统';
结果如下图:
我们发现两种结果相同,证明查询正确。
有些嵌套查询可以用连接运算替代,能够用连接运算表达的査询尽可能采用连接运算。
[例3.57] 找出每个学生超过(或等于)他自己选修课程平均成绩的课程号。
此例题中的子查询为相关子查询(子查询的查询条件以来于父查询),代码如下:
select Sno , Cno
from SC x
where Grade >=
(
select avg(Grade)
from SC y
where y.Sno = x.Sno
);
结果如下图:
- 求解相关子查询和求解不相关子查询不同,内层查询由于与外层查询有关,所以必须反复求值。比如这道题目的一种可能求解过程如下:
- 从外层查询中取出SC的一个元组 x ,将元组 x 的Sno值传递给内层查询。
- 执行内层查询,可以得到一个值,用这个值替代内层查询,代入到外层查询中。
- 执行查询,得到部分结果
- 判断最外层SC元组是否处理完毕。
4.1 没处理完毕时:外层查询取出下一个元组,重复上述步骤。
4.2 处理完毕:输出结果,结束。
[例3.58] 査询非计算机科学系中比计算机科学系任意一个学生年龄小的学生姓名和年龄。
代码如下:
select Sname , Sage
from Student
where Sage < any
(
select Sage
from Student
where Sdept = 'CS'
)
and Sdept <> 'CS';
结果如下图:
- 这里我们用到了 any 谓词,怎么用呢?
- 当子查询返回单个值的时候我们可以用比较运算符,但是返回多个值的时候就要用到我们的 any 或者 all 谓词。
- 使用谓词 any 与 all 必须同时使用 比较运算符。如下表:
符号 | 效果 |
---|---|
> any | 大于子査询结果中的某个值 |
> all | 大于子査询结果中的所有值 |
< any | 小于子査询结果中的某个值 |
< all | 小于子査询结果中的所有值 |
>= any | 大于等于子査询结果中的某个值 |
>= all | 大于等于子査询结果中的所有值 |
<= any | 小于等于子査询结果中的某个值 |
<= all | 小于等于子査询结果中的所有值 |
= any | 等于子査询结果中的某个值 |
= all | 等于子査询结果中的所有值(通常没有实际意义) |
!= any 或 <> any | 不等于子査询结果中的某个值 |
!= all 或 <> all | 不等于子査询结果中的任何一个值 |
- 本题目也可以用聚集函数来实现,代码如下:
select Sname , Sage
from Student
where Sgae <
(
select max(Sage)
from Student
where Sdept = 'CS'
)
and Sdept <> 'CS';
这个结果我就不贴上来了。
[例3.59] 査询非计算机科学系中比计算机科学系所有学生年龄都小的学生姓名及年龄。
这里我们用两种方法来写,代码如下:
select Sname , Sage
from Student
where Sage < all
(
select Sage
from Student
where Sdept = 'CS'
)
and Sdept<> 'CS';
结果如下图:
- 我们很容易知道:此题也可以用聚集函数来解决,代码如下:
select Sname , Sage
from Student where Sage <
(
select min(Sage)
from Student
where Sdept = 'CS'
)
and Sdept <> 'CS';
- 显然使用聚集函数更加容易理解,并且,聚集函数实现子查询的效率相对于使用 any 或者 all 来说高些,他们的对应表如下:
= | <> 或 != | < | <= | > | >= | |
---|---|---|---|---|---|---|
any | in | -- | < max | <= max | > min | >= min |
all | -- | not in | < min | <= min | > max | >= max |