子查询或内部查询或嵌套查询在另一个SQL查询的查询和嵌入式WHERE子句中。
子查询用于返回将被用于在主查询作为条件的数据,以进一步限制要检索的数据。
子查询可以在SELECT,INSERT,UPDATE使用,而且随着运算符如DELETE语句 =, <, >, >=, <=, IN, BETWEEN 等.
这里有一些规则,子查询必须遵循:
-
子查询必须被圆括号括起来。
-
子查询只能在有一列的SELECT子句中,除非多个列中的子查询,以比较其选定列主查询。
-
ORDER BY不能在子查询中使用,主查询可以使用ORDER BY。GROUP BY可以用来在子查询中如ORDER BY执行相同的功能。
-
返回多于一个行子查询只能用于具有多个值运算符,如IN操作。
-
SELECT列表中不能包含到值计算到任何引用 BLOB, ARRAY, CLOB, 或NCLOB.
-
子查询不能立即封闭在一组函数。
-
BETWEEN 操作符不能与子查询使用;然而,操作符BETWEEN可以在子查询中使用。
学习本节所需要的表:
CREATE TABLE TEACHER ( ID INT IDENTITY (1,1) PRIMARY KEY , --主键,自增长 TNO INT NOT NULL, --教工号 TNAME CHAR(10) NOT NULL, --教师姓名 CNO INT NOT NULL, --课程号 SAL INT, --工资 DNAME CHAR(10) NOT NULL, --所在系 TSEX CHAR(2) NOT NULL, --性别 AGE INT NOT NULL --年龄 ) INSERT INTO dbo.TEACHER VALUES( 1,'王军',4,400,'数学','男',32) INSERT INTO dbo.TEACHER VALUES( 2,'李彤',5,6600,'生物','女',54) INSERT INTO dbo.TEACHER VALUES( 3,'王永军',1,1000,'计算机','女',45) INSERT INTO dbo.TEACHER VALUES( 4,'刘晓婧',2,8000,'计算机','女',23) INSERT INTO dbo.TEACHER VALUES( 5,'高维',8,6000,'电子工程','男',54) INSERT INTO dbo.TEACHER VALUES( 6,'李伟',7,230,'机械工程','女',23) INSERT INTO dbo.TEACHER VALUES( 7,'刘辉',3,0,'生物','女',65) INSERT INTO dbo.TEACHER VALUES( 8,'刘伟',9,500,'计算机','男',23) INSERT INTO dbo.TEACHER VALUES( 9,'刘静',12,0,'经济管理','男',45) INSERT INTO dbo.TEACHER VALUES( 10,'刘奕锴',13,70000,'计算机','女',65) INSERT INTO dbo.TEACHER VALUES( 11,'高维',14,70000,'经济管理','男',61) CREATE TABLE COURSE ( ID INT IDENTITY (1,1) PRIMARY KEY , --主键,自增长 CNO INT NOT NULL, --课程号 CNAME CHAR(30) NOT NULL, --课程名称 CTIME INT NOT NULL, --学时 SCOUNT INT NOT NULL, --容纳人数 CTEST SMALLDATETIME NOT NULL, --考试时间 ) INSERT INTO dbo.COURSE VALUES( 4,'应用数学基础',48,120,'2006-7-10') INSERT INTO dbo.COURSE VALUES( 5,'生物工程概论',32,80,'2006-7-8') INSERT INTO dbo.COURSE VALUES( 1,'计算机软件基础',32,70,'2006-7-8') INSERT INTO dbo.COURSE VALUES( 2,'计算机硬件基础',24,80,'2006-6-28') INSERT INTO dbo.COURSE VALUES( 8,'模拟电路设计',28,90,'2006-7-10') INSERT INTO dbo.COURSE VALUES( 7,'机械设计实践',48,68,'2006-7-14') INSERT INTO dbo.COURSE VALUES( 3,'生物化学',32,40,'2006-7-2') INSERT INTO dbo.COURSE VALUES( 9,'数据库设计',16,80,'2006-7-1') INSERT INTO dbo.COURSE VALUES( 6,'设计理论',28,45,'2006-6-30') INSERT INTO dbo.COURSE VALUES( 10,'计算机入门',25,150,'2006-6-29') INSERT INTO dbo.COURSE VALUES( 11,'数字电路设计基础',30,125,'2006-6-20') CREATE TABLE STUDENT ( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL, SNO CHAR(4) NOT NULL, --学号 SNAME CHAR(10) NOT NULL, --姓名 DNAME CHAR(10) NOT NULL, --系 SSEX CHAR(2) NOT NULL, --性别 CNO INT , --课程号 MARK DECIMAL(3,1), --成绩 TYPE CHAR(4) --课程类型 ) INSERT INTO dbo.STUDENT VALUES('9701','刘建国','管理工程','男',4,82.5,'必修') INSERT INTO dbo.STUDENT VALUES('9701','刘建国','管理工程','男',10,70,'选修') INSERT INTO dbo.STUDENT VALUES('9701','刘建国','管理工程','男',1,78.5,'选修') INSERT INTO dbo.STUDENT VALUES('9702','李春','环境工程','女',5,63,'必修') INSERT INTO dbo.STUDENT VALUES('9702','李春','环境工程','女',10,58,'选修') INSERT INTO dbo.STUDENT VALUES('9703','王天','生物','男',5,48.5,'必修') INSERT INTO dbo.STUDENT VALUES('9703','王天','生物','男',2,86,'选修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',4,76,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',1,92,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',2,89,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',9,80,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',8,70,'选修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',8,79,'必修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',1,59,'必修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',11,52,'必修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',6,68,'必修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',13,93,'必修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',12,88.5,'必修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',1,78,'选修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',10,76,'选修') 数据表
表数据
--在多表查询中使用子查询 --采用连接表的方法 SELECT s.CNO,SNO,SNAME,DNAME FROM dbo.STUDENT AS s INNER JOIN dbo.COURSE AS c ON s.CNO = c.CNO WHERE c.CNAME='计算机入门' --采用子查询 SELECT CNO,SNO,SNAME,DNAME FROM dbo.STUDENT WHERE CNO=(SELECT CNO FROM dbo.COURSE WHERE CNAME='计算机入门')
--在子查询中使用聚合函数 --使用平均值函数 SELECT * FROM dbo.TEACHER WHERE AGE>(SELECT AVG(AGE) FROM dbo.TEACHER) --比较判式两边均采用聚合分析的字查询
--TEACHER中查询教师的教工号等信息,并且要求教师所在系的平均年龄大于所有老师的平均年龄 SELECT t.TNO,t.TNAME,t.DNAME,t.CNO,t.AGE FROM dbo.TEACHER AS t WHERE (SELECT AVG(AGE) FROM dbo.TEACHER WHERE DNAME=t.DNAME)>(SELECT AVG(AGE) FROM dbo.TEACHER)
--在where子句中使用子查询
--在TEACHER中查询所有教师的教工号等,以及在STUDENT中修了这门课程的学生人数 SELECT TNO,TNAME,DNAME,CNO,(SELECT COUNT(*) FROM dbo.STUDENT WHERE CNO=dbo.TEACHER.CNO) AS S_NUN FROM dbo.TEACHER ORDER BY S_NUN
介绍一下代码的执行过程 1、首先DBMS读取TEACHER表中的一行教师信息数据,来执行主查询。 SELECT TNO,TNAME,DNAME,CNO FROM dbo.TEACHER 2、在从TEACHER表中提取了教师信息之后,DBMS将该行的TNO、TNAME、DNAME、CNO字段信息添加到结果表中,然后执行子查询获取STUDENT表中修改该教师开设课程的学生人数。 (SELECT COUNT(*) FROM dbo.STUDENT WHERE CNO=dbo.TEACHER.CNO) 3、在子查询的WHERE子句中,CNO为子查询的表STUDENT表中的列,而TEACHER.CNO为TEACHER表中的CNO列.他们的列名相同,因此对于子查询外表(即TEACHER表)中的CNO列我们一定要指明表名。 注意:在SELECT子句中使用子查询时,子查询必须返回单值。
--创建和使用返回多行的子查询 --一个子查询除了可以产生一个单值外,也可以产生一个关系,该关系可以包括若干元组。SQL提供了若干对于关系的操作符,主要包括:IN、EXISTS、SOME(ANY)、ALL、UNIQUE等。 --IN子查询 用法: SELECT column_name FROM table_name WHERE test expression [NOT] IN (subquery) test expression可以是实际值、列名、表达式或是另一个返回单一值的子查询。IN运算符前面加上NOT关键字,表示与集合成员不匹配时,NOT IN判别式求值为True。 --查询不及格的课程的姓名、系、所有的课程及成绩信息 SELECT s1.SNAME,s1.DNAME,s1.CNO,s1.MARK,s1.SNO FROM dbo.STUDENT AS s1,dbo.STUDENT AS s2 WHERE s1.SNO=s2.SNO AND s1.MARK<60 ORDER BY s1.SNAME --采用IN子查询的写法 SELECT SNAME,DNAME,CNO,MARK,SNO FROM dbo.STUDENT WHERE SNO IN(SELECT SNO FROM dbo.STUDENT WHERE MARK<60) ORDER BY SNAME
分析代码执行的过程 1、执行一个查询时,DBMS首先处理最里面的子查询。系统首先执行"SELECT SNO FROM dbo.STUDENT WHERE MARK<60"语句,生成STUDENT表中成绩小于60的学生的学号SNO集。 2、DBMS一次处理STUDENT表中的一行记录,并且将每行记录中的SNO列值与子查询结果集中的SNO值进行比较。 3、如果在系统子查询结果中找到了与正在处理的记录中的SNO值相匹配的值,WHERE子句求值为True,DBMS则将该记录的相关信息归入结果表。 4、如果在子查询结果集中没有发现与记录的SNO值相匹配的值,WHERE子句求值为False,DBMS转而去处理STUDENT表中的下一行而舍弃该行数据。
--IN子查询实现集合交和集合差运算 --采用IN子查询实现集合交运算 SELECT CNO,SNO,SNAME,DNAME FROM dbo.STUDENT WHERE CNO=1 AND SNO IN(SELECT SNO FROM dbo.STUDENT WHERE CNO=10) --查询修过1号或者10号课程的学生信息--采用IN子查询实现集合差运算 SELECT CNO,SNO,SNAME,DNAME FROM dbo.STUDENT WHERE CNO=1 AND SNO NOT IN(SELECT SNO FROM dbo.STUDENT WHERE CNO=10) --查询修过1号但是没有修10号课程的学生信息
注意:由运算符IN引入的子查询返回的列值,既可以来自主查询的表,也可以来自其他表。SQL对查询的唯一要求就是它必须返回单一列的数据值,并且其数据类型必须与IN前面的表达式的数据类型相兼容。
--EXISTS子查询 --前言:在某些情况下,我们只需要子查询返回一个True或者False,子查询数据内容本身并不重要,这时,可使用EXISTS判式来定义子查询。EXISTS判式用来测试集合是否为空,它总是与子查询结合使用,而且只要子查询中至少返回一个值,EXISTS判式的值就为True。如果子查询的表中没有值(表中没有行满足子查询的WHERE子句的搜索条件),那么EXISTS判式的值为False。 用法: SELECT column_name FROM table_name WHERE [NOT] EXISTS (subquery) 只要子查询(subquery)中返回的结果表存在记录,EXISTS (subquery)就返回True,WHERE子句的结果也就为True。如果使用[NOT] EXISTS判式,当子查询返回的结果没有记录行时,其返回True。 --使用EXISTS子查询 SELECT TNO,TNAME,DNAME,CNO FROM dbo.TEACHER AS t WHERE EXISTS(SELECT * FROM dbo.STUDENT WHERE CNO=t.CNO) ORDER BY t.TNO --查询开设的课程有学生修过的教师信息
--使用NOT EXISTS子查询 SELECT CNO,CNAME,CTIME,CTEST FROM dbo.COURSE AS c WHERE NOT EXISTS(SELECT * FROM dbo.STUDENT WHERE CNO=c.CNO) --查询学生表中未被选修的课程信息 --使用IN子查询: SELECT CNO,CNAME,CTIME,CTEST FROM dbo.COURSE WHERE CNO NOT IN(SELECT CNO FROM dbo.STUDENT)
--EXISTS子查询实现两表交集 SELECT CNO,TNAME,DNAME FROM dbo.TEACHER WHERE TSEX='女' AND EXISTS(SELECT * FROM dbo.COURSE WHERE MONTH(CTEST)=7 AND CNO=dbo.TEACHER.CNO) ORDER BY CNO --女教师开设7月份课程的交集 --也可以用INNER JOIN SELECT t.CNO,t.TNAME,t.DNAME FROM dbo.TEACHER AS t INNER JOIN dbo.COURSE AS c ON c.CNO = t.CNO AND t.TSEX='女' AND MONTH(c.CTEST)=7 ORDER BY c.CNO
--SOME/ALL子查询 --只要我们使用了SQL比较运算符(=、<、>、<=、>=)中的一个来比较两个表达式的值,那么运算符前后的表达式都必须为单一值。而数量词SOME、ANY、ALL则允许使用比较运算符将单值与子查询返回的值加以比较,这里的子查询返回的结果可以是多行的。 用法: SELECT column_name FROM table_name WHERE test expression # SOME/ALL (subquery) test expression可以是实际值、列名、表达式或是另一个返回单一的子查询。
表达式 | 含义 |
>SOME | 大于子查询结果中的某个值 |
>ALL | 大于子查询结果中的所有值 |
<SOME | 小于子查询结果中的某个值 |
<ALL | 小于子查询结果中的所有值 |
>=SOME | 大于等于子查询结果中的某个值 |
>=ALL | 大于等于子查询结果中的所有值 |
<=SOME | 小于等于子查询结果中的某个值 |
<=ALL | 小于等于子查询结果中的所有值 |
=SOME | 等于子查询结果中的某个值 |
=ALL | 等于子查询结果中的所有值(通常没有实际意义) |
!=SOME | 不等于子查询结果中的某个值 |
!=ALL | 不等于子查询结果中的任何一个值 |
--SOME/ALL子查询 SELECT SNO,AVG(MARK) AS 成绩 FROM dbo.STUDENT GROUP BY SNO HAVING AVG(MARK)>=ALL(SELECT AVG(MARK) FROM dbo.STUDENT GROUP BY SNO) --平均成绩最高的学生
分析执行过程: 1、执行查询时,DBMS首先根据学号SNO,将STUDENT表中的数据分组,得到分组后的中间表。 2、接着执行HAVING子句,从分组后的中间表中取出一组,执行AVG(MARK)聚合函数,得到平均成绩。 3、而后系统开始执行子查询,子查询返回的结果为STUDENT表中每个同学的平均成绩。 4、如果2取出的这组同学的平均成绩小于3得到的结果表中的所有记录,则DBMS转而处理下一组数据;而如果满足条件,则DBMS将该组记录的学号和平均成绩添加到主查询的结果表中,而后继续处理下一组数据。 注意:关键字SOME和关键字ANY具有完全相同的功能
--SOME/ALL子查询的使用 --查询其他系中比经济管理系中所有教师年龄都小的教师信息 SELECT TNO,TNAME,DNAME,AGE FROM dbo.TEACHER WHERE AGE<ALL(SELECT AGE FROM dbo.TEACHER WHERE DNAME='经济管理 ') AND DNAME<>'经济管理 ' ORDER BY AGE --采用聚集函数来实现 SELECT TNO,TNAME,DNAME,AGE FROM dbo.TEACHER WHERE AGE<(SELECT MIN(AGE) FROM dbo.TEACHER WHERE DNAME='经济管理') AND DNAME<>'经济管理' ORDER BY AGE
实际上,聚集函数实现子查询通常比直接用SOME或All查询效率更高。SOME和ALL与聚集函数关系如下:
表达式 | 对应的聚集函数 |
>SOME | >MIN |
>ALL | >MAX |
<SOME | <MIN |
<ALL | <MAX |
>=SOME | >=MIN |
>=ALL | >=MAX |
<=SOME | <=MAX |
<=ALL | <=MIN |
=SOME | IN |
=ALL | 无 |
!=SOME | 无 |
!=ALL | NOT IN |
--相关子查询 --由前面介绍的一些子查询我们不难发现,有些复杂的子查询需要执行若干次,因为每次执行时,都需要来自子查询外部的元组变量的值,也就是说,子查询的执行要依赖于上层查询元组的当前值,我们将这种子查询称之为相关子查询。 --使用IN引入相关子查询 SELECT CNO,CNAME,CTIME,CTEST FROM dbo.COURSE WHERE '李华' IN (SELECT SNAME FROM dbo.STUDENT WHERE CNO=dbo.COURSE.CNO) --查询李华的信息 --采用表的连接 SELECT c.CNO,c.CNAME,c.CTIME,c.CTEST FROM dbo.COURSE AS c INNER JOIN dbo.STUDENT AS s ON s.CNO = c.CNO WHERE s.SNAME='李华' ORDER BY c.CNO
--比较运算符引入相关子查询 SELECT s.SNAME,s.DNAME,s.CNO,s.MARK FROM dbo.STUDENT AS s WHERE (SELECT CTEST FROM dbo.COURSE WHERE CNO=s.CNO)<CAST('2006-7-2' AS SMALLDATETIME) ORDER BY s.SNAME --查询时间小于2006-7-2,的信息 --采用表的连接 SELECT s.SNAME,s.DNAME,s.CNO,s.MARK FROM dbo.STUDENT AS s INNER JOIN dbo.COURSE AS c ON c.CNO = s.CNO WHERE CTEST<'2006-7-2' ORDER BY s.SNAME
注意:二者实现功能一样,采用表连接的方式对数据的查询更加灵活。因为采用相关子查询,所查询的列只能是主查询中FROM子句的列,不能查其他列,而采用多表连接则不存在这个问题。 --在HAVING子句中使用相关子查询 --在老师表中查询包含的系的名称,要求这些系中,各系包含的教师数目要少于学生表中修改该系的教师开设课程的学生的人次。 --假如老师表中,A系有m个教师,而在学生表中有n人次修过这m个教师开设的课程,如果n>m,则A系就符合查询条件。 SELECT t.DNAME FROM dbo.TEACHER AS t GROUP BY t.DNAME HAVING COUNT(*)<(SELECT COUNT(*) FROM dbo.STUDENT WHERE CNO IN(SELECT CNO FROM dbo.TEACHER AS t2 WHERE t2.DNAME=t.DNAME)) ORDER BY t.DNAME
分析执行过程: 1、执行该select语句时,DBMS首先执行FROM和GROUP BY子句,将TEACHER表中的记录依据DNAME字段进行分组,形成中间表(虚拟表)。该中间表从概念上可理解为图一,其中共有6个分组。
2、接着系统处理来自中间表的一个分组信息,即依次对每个系名DNAME执行HAVING子句,这里假定取出第一组,即DNAME为‘电子工程’。 3、HAVING子句中,比较运算符右边是一个相关子查询,而相关子查询中又含有一个子查询。系统将从最底层的子查询“SELECT CNO FROM dbo.TEACHER AS t2 WHERE t2.DNAME=t.DNAME”开始执行,即查询TEACHER表中电子工程系的所有教师所授课程的课程号,从图一见,只有一个,即‘8’。 4、而后系统将执行其外层的相关子查询,即计算STUDENT表中所有修过8号课程的记录数量。这时执行如下语句:SELECT COUNT(*) FROM dbo.STUDENT WHERE CNO IN(8),如图结果:
所以最终相关子查询的结果是2,而比较运算符左边为计算DNAME为‘电子工程’这组的记录条目,从图一知,只有一个记录,结果为1。显然1<2的判别式成立,HAVING子句返回TRUE,‘电子工程’就被写入结果表中。接着系统按照同样的方式依次处理图一中的第2组、第3组......
--嵌套子查询 --位于子查询内的子查询被称为嵌套的子查询--在COURSE中查询计算机系教师开设课程信息,并且要求在STUDENT中所有修过这门课程的同学的成绩都及格 SELECT * FROM dbo.COURSE WHERE CNO IN (SELECT CNO FROM dbo.TEACHER WHERE DNAME='计算机' AND NOT EXISTS(SELECT * FROM dbo.STUDENT WHERE CNO=dbo.TEACHER.CNO AND MARK<60)) ORDER BY CNO --通过两个子查询来实现 SELECT * FROM dbo.COURSE WHERE CNO IN(SELECT CNO FROM dbo.TEACHER WHERE DNAME='计算机') AND NOT EXISTS (SELECT * FROM dbo.STUDENT WHERE CNO=dbo.COURSE.CNO AND MARK<60) ORDER BY CNO
分析:在主查询WHERE子句中通过IN关键字引入一个子查询,该子查询实现的功能是:搜索那些在TEACHER表中由计算机系教师开设的、并且在STUDENT表中没有同学不及格的课程号;而在该子查询内SELECT语句的WHERE子句中,又通过NOT EXISTS关键词引入了一个子查询,该子查询即为嵌套子查询,用来判断计算机系教师开设的课程在STUDENT中是否有同学不及格。
--使用子查询创建视图 CREATE VIEW <view_name> [(column1,column2...)] AS SELECT <column_name> FROM <table_name> 前面博客有讲到视图的用法,可以去看http://www.cnblogs.com/liuchenxing/p/8251932.html
--创建视图,包含学生表中课程号和课程平均成绩 CREATE VIEW VIEW_AVGMARK(CNO,AVG_MARK) AS SELECT CNO,AVG(MARK) FROM dbo.STUDENT GROUP BY CNO --查询这个视图 SELECT * FROM VIEW_AVGMARK
--在COURSE表中查询在STUDENT中平均成绩高于70的课程的课程号和信息 SELECT CNO,CNAME,CTEST FROM dbo.COURSE WHERE CNO IN (SELECT CNO FROM VIEW_AVGMARK WHERE AVG_MARK>70) ORDER BY CNO
--不采用视图,对比结果,用视图显得更直观,更简化语句 SELECT CNO,CNAME,CTEST FROM dbo.COURSE WHERE CNO IN (SELECT CNO FROM dbo.STUDENT GROUP BY CNO HAVING AVG(MARK)>70) ORDER BY CNO