子查询(Subsquery)
一、子查询的概念
将一个查询块嵌套在另一个查询块的 WHERE 子句或 HAVING 短语的条件中的查询称为子查询。一个 SELECT-FROM-WHERE 语句称为一个查询块。
例如:select sname from student where sno in (select studentid from sc where courseid=2);
子查询返回的数据通常在主查询里作为一个条件,从而进一步限制数据库返回的数据。它可以用于 SELECT、INSERT、UPDATE 和 DELETE 语句。
在某些情况下,子查询能够间接地基于一个或多个条件把多个表里的数据关联起来,从而代替结合操作。当查询里使用子查询时,子查询首先被执行,然后主查询再根据子查询返回的结果执行。子查询可以用于主查询的 WHERE 或 HAVING 子句。逻辑和关系操作符,比如=、>、<、<>、!=、IN、NOT IN、AND、OR,可以用于子查询里,也可以在 WHERE 或 HAVING 子句里对子查询进行操作。
注意:子查询规则——标准查询的规则同样适用于子查询,结合操作、函数、转换和其他选项都可以在子查询里使用。
二、子查询必须遵守的规则
1、子查询必须位于圆括号里。
2、除非主查询里有多个字段让子查询进行比较,否则子查询的 SELECT 子句里只能有一个字段。
3、子查询里不能使用 ORDER BY 子句。在子查询里,我们可以用 GROUP BY 子句实现 ORDER BY 功能。
4、返回多条记录的子查询只能与多值操作符(比如 IN)配合使用。
5、子查询不能直接被包围在函数里。
6、操作符 BETWEEN 不能用于子查询,但子查询内部可以使用它。
二、子查询的分类
按照内外层是否进行连接划分,子查询可分为嵌套子查询和相关子查询。
1、嵌套子查询
嵌套子查询——子查询独立,不受主查询影响,运算完成后返回结果给主查询。
例1:查询雇员 ID 是1 的上级领导所管理的所有员工数据:
SELECT * FROM demo_employee WHERE 上级=(SELECT 上级 FROM demo_empoyee WHERE 雇员ID=1);
子查询可以嵌入到另一个子查询里,就象子查询嵌套在普通查询里一样。在有子查询时,子查询先于主查询执行。类似地,在嵌套的子查询里,最内层的子查询先被执行,然后再依次执行外层的子查询,直到主查询。
2、相关子查询
相关子查询——主查询将必要信息传入子查询对比,符合的子查询数据才会被外层查询返回结果。
例2:查询雇员ID是1 的上级领导所管理的所有员工数据: SELECT * FROM demo_employee E WHERE EXISTS (SELECT 1 FROM demo_employee D WHERE E.上级=D.上级 AND D.雇员ID=1);
子查询可以与数据操作语言(DML)配合使用。下面我们以数据库 learnsql 中的表做例子加以说明。
三、在 INSERT 语句中使用子查询
INSERT 语句将子查询返回的结果插入到另一个表。
例3:把小时工资高于雇员220984332的所有雇员的 EMP_ID、LAST_NAME、FIRST_NAME 和 PAY_RATE 插入到一个名为 RICH_EMPLOYEES 的表里:
INSERT INTO RICH_EMPLOYEES
SELECT E.EMP_ID, E.LAST_NAME, E.FIRST_NAME, EP.PAY_RATE FROM EMPLOYEE_TBL E, EMPLOYEE_PAY_TBL EP WHERE E.EMP_ID=EP.EMP_ID AND EP.PAY_RATE>(SELECT PAY_RATE FROM EMPLOYEE_PAY_TBL WHERE EMP_ID='220984332');
四、在 UPDATE 语句中使用子查询
子查询可以与 UPDATE 语句配合使用来更新一个表里的一个或多个字段。
例4:把居住在 Indianapolis 的全部雇员的小时工资提高 10%:
UPDATE EMPLOYEE_PAY_TBL
SET PAY_RATE=PAY_RATE*1.1
WHERE EMP_ID IN (SELECT EMP_ID FROM EMPLOYEE_TBL WHERE CITY='INDIANAPOLIS');
这个子查询返回多条记录,因此要使用操作符 IN 而不是等号。
五、在 DELETE 语句中使用子查询
子查询也可以与 DELETE 语句配合使用。
例5:从 EMPLOYEE_PAY_TBL 表里删除 BRANDON GLASS 的记录,由于不知道他的标识号码,我们可以利用一个子查询,根据名字从 EMPLOYEE_TBL 表里获取他的标识号码:
DELETE FROM EMPLOYEE_PAY_TBL
WHERE EMP_ID=(SELECT EMP_IDFROM EMPLOYEE_TBL
WHERE LAST_NAME='GLASS'
AND FIRST_NAME='BRANDON');
六、子查询举例
有学生表 Student(Sno, Sname, Sage, Ssex),包括学号、姓名、年龄、性别四个字段;
课程表 Course(Cno, Cname, Tno),包括课程编号、课程名、教师编号三个字段;
成绩表 SC(Sno, Cno, Score),包括学号、课程编号、成绩三个字段;
教师表 Teacher(Tno, Tname),包括教师编号、姓名两个字段。
完成如下查询:
1、查询全部学生都选修的课程的课程号和课程名
Select Cno, Cname
From Course
Where Cno in (select Cno from SC
group by Cno);
2、查询没学过"叶平"老师讲授的任一门课程的学生姓名
Select Sname
From Student
Where Sno not in (select Sno from Course,Teacher,SC
where Course.Tno=Teacher.Tno and SC.Cno=course.Cno
and Tname='叶平');
3、查询所有课程成绩小于60分的同学的学号、姓名;
Select Sno,Sname
From Student
Where Sno not in (select S.Sno
from Student S, SC
where S.Sno=SC.Sno and SC.Score>=60);
4、查询有两门以上课程不及格的学生的学号及其平均成绩
Select Sno, avg(Score)
From SC
Where Sno in (select Sno from SC
where Score<60
group by Sno
having count(*)>2)
Group by Sno;
5、查询没有学全所有课的同学的学号、姓名;
Select Student.Sno, Student.Sname
From Student,SC
Where Student.Sno=SC.Sno
Group by Student.Sno, Student.Sname
Having count(SC.Cno)<(select count(Cno) from Course);
6、查询学过编号"001"并且也学过编号"002"课程的同学的学号、姓名
Select Student.Sno, Student.Sname
From Student,SC
Where Studet.Sno=SC.Sno and SC.Cno='001'
and exists (Select * from SC as SC_2
where SC_2.Sno=SC.Sno and SC_2.Cno='002');
7、查询至少有一门课与学号为"1001"的同学所学相同的同学的学号和姓名;
Select S.Sno, S.Sname
From Student S, SC
Where S.Sno=SC.Sno
and SC.Cno in (select Cno from SC
where Sno='1001');
8、查询学过学号为"001"同学所学一门课的其他同学的学号和姓名;
Select distinct S.Sno, S.Sname
From Student S, SC
Where S.Sno=SC.Sno
and Cno in (select Cno from SC
where Sno='001');
9、查询和"1002"号同学学习的课程完全相同的其他同学的学号和姓名;
Select Sno from SC
Where Cno in (select Cno from SC where Sno='1002')
group by Sno
having count(*)=(select count(*) from SC where Sno='1002');
10、向 SC 表中插入一些记录,这些记录要求符合以下条件:没有上过编号"003"课程的同学学号、"002"号课的平均成绩;
Insert SC
Select Sno, '002', (select avg(Score) from SC where Cno='002')
From Student
Where Sno not in (select Sno from SC where Cno='003');
11、查询没学过"叶平"老师课的同学的学号、姓名
Select Student.Sno, Student.Sname
From Student
Where Sno not in (Select distinct SC.Sno
From SC,Course,Teacher
Where SC.Cno=Course.Cno
and Teacher.Tno=Course.Tno
and Teacher.Tname='叶平');
12、查询学过"叶平"老师所教的所有课的同学的学号、姓名;
Select Sno,Sname
From Student
Where Sno in (select Sno from SC,Course,Teacher
where SC.Cno=Course.Cno
And Teacher.Tno=Course.Tno
and Teacher.Tname='叶平'
group by Sno
having Count(SC.Cno)=(select count(Cno) From Course,Teacher
Where Teacher.Tno=Course.Tno
and Tname='叶平') );
13、查询选修"叶平"老师所授课程的学生中,成绩最高的学生姓名及其成绩
Select Student.Sname,SC.Score
From Student,SC,Course C,Teacher
Where Student.Sno=SC.Sno and SC.Cno=C.Cno
and C.Tno=Teacher.Tno
and Teacher.Tname='叶平'
and SC.score=(select max(score) from SC
where Cno=C.Cno);
子查询和连接查询可以结合使用,完成许多复杂的数据库查询任务。本章讲的内容是我们课程的重点,希望大家好好掌握。
课后练习:
1. 在用于 SELECT 语句时,子查询的功能是什么?
2. 在子查询与 UPDATE 语句配合使用时,能够更新多个字段吗?
3. 下面的语法正确吗?如果不正确,正确的语法应该是怎样的?
a.
SELECT CUST_ID, CUST_NAME
FROM CUSTOMER_TBL
WHERE CUST_ID =
(SELECT CUST_ID
FROM ORDERS_TBL
WHERE ORD_NUM = '16C17');
b.
SELECT EMP_ID, SALARY
FROM EMPLOYEE_TBL
WHERE SALARY BETWEEN '20000'
AND (SELECT SALARY
FROM EMPLOYEE_ID
WHERE SALARY = '40000');
c.
UPDATE PRODUCTS_TBL
SET COST = 1.15
WHERE CUST_ID =
(SELECT CUST_ID
FROM ORDERS_TBL
WHERE ORD_NUM = '32A132');
4. 下面语句执行的结果是什么?
DELETE FROM EMPLOYEE_TBL
WHERE EMP_ID IN
(SELECT EMP_ID
FROM EMPLOUEE_PAY_TBL);
5. 使用数据库 learnsql 完成练习:
1)更新表 CUSTOMER_TBL,找到 ORD_NUM 列中订单号为23E934的顾客,把顾客名称修改为 DAVIDS MARKET。
2)查询小时工资高于 JOHN DOE 的全部雇员的姓名,JOHN DOE 的雇员标识号码是343559876。
3)列出所有价格高于全部产品平均价格的产品。
6. 使用数据库 company 完成下面的子查询练习:
1)查询每个部门中工资最高的人的姓名、薪水和部门编号。(提示:先求出每个部门中的最高工资,再使用连接查询。)
2)查询每个部门平均工资所在的等级。
3)查询每个部门内平均的薪水等级。
4)查询雇员中有哪些人是经理人。
5)不准用库函数,求雇员表中薪水的最高值。
6)查询平均薪水最高的部门的部门编号。
(提示: ① 先求出每个部门的平均薪水和部门号(把这个看成一张表);
② 再求出平均薪水最高值(把这个看成一个值);
③ 对①表使用②条件进行查询即可。)
7)求平均薪水最高的部门的部门名称(在子查询6的基础之上)。
7. 使用数据库数据库 stuscore 完成下面的子查询练习:
1)查询出所有大连地区的学生的成绩。
2)查询从来没有参加任何考试的学生的信息。
3)查询有考试成绩纪录的学生的信息.
4)将所有大连地区学生的课程编号为3的课程成绩加10分。
5)删除所有C#课的成绩。
6)查询课程编号为3的课程的平均成绩,显示有此门课程成绩的学生编号、成绩及距平均分之间的差值。
7)查询参加了所有考试的学生的姓名。
8)查询有成绩纪录的学生人数。
9)查询有2门以上成绩的学生的姓名和联系方式。
10)查询哪些学生有c#考试成绩,显示这些学生的姓名。
11)查询出科目中教师的姓名,同时增加编号。
12)查询出联系电话为null的学生信息,联系电话用'无'代替。
13)将查询出的地区为大连的都显示为null.
14)查询科目平均成绩超过70分的教师姓名。
8. 在4.1节的练习1中,我们为数据库创建的四个表----学生表(Student)、课程表(Course)、成绩表(Score)以及教师信息表(Teacher)填充了数据。在此基础上完成以下查询:
1)查询Score表中分数最高的学生学号和课程号。
2)假设使用如下命令建立了一个grade表:
create table grade (low int(3), upp int(3), rank char(1));
insert into grade values(90,100,'A');
insert into grade values(80,89,'B');
insert into grade values(70,79,'C');
insert into grade values(60,69,'D');
insert into grade values(0,59,'E');
现查询所有同学的Sno、Cno和rank列。
3)查询选修"3-105"课程的成绩高于"109"号同学成绩的所有同学的记录。
4)查询score中选学多门课程的同学中分数为非最高分成绩的记录。
5)查询成绩高于学号为"109"、课程号为"3-105"的成绩的所有记录。
6)查询和学号为108的同学同年出生的所有学生的Sno、Sname和Sbirthday列。
7)查询"张旭"教师任课的学生成绩。
8)查询选修某课程的同学人数多于5人的教师姓名。
9)查询选修编号为"3-105"课程且成绩至少高于选修编号为"3-245"的同学的Cno、Sno和Degree,并按Degree从高到低次序排序。
10)查询选修编号为"3-105"且成绩高于选修编号为"3-245"课程的同学的Cno、Sno和Degree.
11)查询成绩比该课程平均成绩低的同学的成绩表。
12)查询所有任课教师的Tname和Depart.
13)查询所有未讲课的教师的Tname和Depart.
14)查询和"李军"同性别的所有同学的Sname.
15)查询和"李军"同性别并同班的同学Sname.
16)查询所有选修"计算机导论"课程的"男"同学的成绩表。