1.1.7 子查询

子查询就是嵌套在另一个SELECT语句中的查询。在SELECT语句中,WHERE子句或者HAVING子句中的条件往往不能用一个确定的表达式来确定,而要依赖于另一个查询,这个被嵌套使用的查询就是子查询,它在形式上是被一对圆括号限定的SELECT语句。在子查询中还可以再嵌套子查询。

例如,要查询所有在部门RESEARCH工作的员工姓名。如果使用常规的查询方法,要进行两次查询,首先查询dept表,确定该部门的部门号,然后根据这个部门号在emp表中查询属于这个部门的员工。也就是说,需要两条SELECT语句:


SQL>SELECT deptno FROM dept WHERE dname='RESEARCH';

SQL>SELECT ename FROM emp WHERE deptno=20;(部门RESEARCH的部门号)


连接这两条SELECT语句的纽带是中间结果—部门号(deptno)。要完成这样的查询,不得不需要人工干预,在两条SELECT语句中传递参数。如果利用子查询,这个问题就迎刃而解。能够完成这个查询功能的一条SELECT语句为:


SQL>SELECT ename FROM emp

WHERE deptno=(SELECT deptno FROM dept WHERE dname='RESEARCH');


这种复杂的SELECT语句的执行过程为:首先执行子查询,将执行的结果返回给主查询,然后再根据条件执行主查询。

子查询一般出现在SELECT语句的WHERE子句或HAVING子句中,作为条件表达式的一部分。子查询的结果是返回一行或多行数据,可以被看做一个集合。条件表达式就是要将某个表达式与这个集合中的元素进行某种比较运算,根据运算的结果是真或是假来决定是否执行上一层查询。常用的运算符如表1.6所示。

figure_0032_0008

其中EXISTS运算符测试子查询的返回结果,只要结果不为空,条件就为真,而主查询和子查询之间可能没有直接关系。例如,在下面的查询语句中,因为子查询返回的结果为空,条件为假,所以上主查询也返回空。


SQL>SELECT ename FROM emp

WHERE exists(SELECT deptno FROM dept WHERE deptno=0);


IN运算符将某个列的值与子查询的返回结果进行比较,只要与其中的一个结果相等,条件即为真。例如,要查询所有出现在emp表中的部门名称,即至少有一名员工的部门,构造的SELECT语句为:


SQL>SELECT dname FROM dept

WHERE deptno IN(SELECT distinct deptno FROM emp);


“=”运算符号比较特殊,它将某个列的值与集合中的元素进行精确匹配。如果子查询只返回单行结果,那么将这个列与这一行进行比较。如果子查询返回多行结果,那么必须用ANY或ALL进行限定,否则将出错。ANY运算符的作用是,只要列值与返回结果中的任何一个相等,条件即为真。ALL运算符的作用是,列值要与返回结果中的所有行都要进行比较。例如,要查询所有在emp表中出现的部门名称,即至少有一名员工的部门,也可以使用下面的SELECT语句:


SQL>SELECT dname FROM dept

WHERE deptno=any(SELECT distinct deptno FROM emp);


运算符>、<、>=和<=与=的用法相似,子查询可以返回单行结果,也可以返回多行结果。如果是多行结果,必须用ANY或ALL进行限定。下面我们再考察几个例子,比较ANY和ALL之间的区别。

如果要查询这样的员工姓名,他的工资高于部门30中的每个员工,相应的SELECT语句为:


SQL>SELECT ename FROM emp

WHERE sal>all(SELECT sal FROM emp WHERE deptno=30);


如果要查询这样的员工姓名,他的工资不低于部门30中的最低工资。也就是说,工资高于部门30中其中任何一个员工即可。相应的SELECT语句为:


SQL>SELECT ename FROM emp

WHERE sal>any(SELECT sal FROM emp WHERE deptno=30);


在子查询中还可以使用分组函数。例如,要查询所有比公司全部员工平均工资高的员工姓名,构造的SELECT语句为:


SQL>SELECT ename FROM emp

WHERE sal>(SELECT avg(sal)FROM emp);


如果要查询这样的部门名称,它的平均工资高于其他部门的平均工资,这样的查询需要使用两次子查询。首先查询其他部门的平均工资,然后根据查询的结果查询其平均工资高于这个结果的部门号,最后根据这个部门号查询它的部门名称。相应的SELECT语句如下所示:


SELECT dname FROM dept

WHERE deptno=(SELECT deptno FROM emp a

GROUP BY deptno

HAVING avg(sal)>all(SELECT avg(sal)FROM emp

WHERE deptno!=a.deptno

GROUP BY deptno))


在这条SELECT语句中,最后一个子查询最先执行,用来求得其他部门的平均工资。然后执行上一个子查询,返回平均工资高于其他部门平均工资的部门号。最后执行最上层的查询,返回这个部门的名称。