oracle之复杂查询(下):子查询

复杂查询(下):子查询

8. 1 非关联子查询:返回的值可以被外部查询使用。子查询可以独立执行的(且仅执行一次)。

8.1.1 单行单列子查询,子查询仅返回一个值,也称为标量子查询,采用单行比较运算符(>,<,=,<>,>=,<=)

例:内部SELECT子句只返回一行结果

SQL>select ename,sal
from emp
where sal > (
     select sal from emp
     where ename='JONES')
/
例:和员工7369从事相同工作并且工资大于员工7876的员工的姓名和工作

SQL>select ename,job,sal
from emp
where job=(
        select job
        from emp
        where empno=7369
        )
and
       sal > (
        select sal
        from emp
        where empno=7876
        )
/

8.1.2 多行单列子查询,采用多行比较运算符(all, any, in,not in)

all  (>大于最大的,<小于最小的)

SQL> select ename,sal from emp where sal >all (2000,3000,4000);

ENAME             SAL
---------- ----------
KING             5000


例:查找高于所有部门的平均工资的员工(>比子查询中返回的列表中最大的大才行)

SQL> select ename, job, sal from emp where sal > all(select avg(sal) from emp group by deptno);


ENAME      JOB              SAL
---------- --------- ----------
JONES      MANAGER        2975
SCOTT      ANALYST         3000
KING       PRESIDENT       5000
FORD       ANALYST         3000

SQL> select avg(sal) from emp group by deptno;   //子查询结果

  AVG(SAL)
----------
1566.66667
      2175
2916.66667

8.1.3 在多行子查询中使用any (>大于最小的,<小于最大的)

>any的意思是:比子查询中返回的列表中最小的大就行, 注意和all的区别,all的条件苛刻,any的条件松阔,
any强调的是只要有任意一个符合就行了,所以>any只要比最小的那个大就行了,没必要比最大的还大。

select ename, sal from emp where sal >any (2000,3000,4000);

ENAME             SAL
---------- ----------
JONES            2975
BLAKE            2850
CLARK            2450
SCOTT            3000
KING              5000
FORD             3000

8.1.4 在多行子查询中使用in  (逐个比较是否有匹配值)

SQL> select ename, sal from emp where sal in (800,3000,4000);

ENAME             SAL
---------- ----------
SMITH             800
SCOTT            3000
FORD             3000

NOT运算操作符可以使用在IN操作上,但不能使用在ANY,ALL操作。

SQL> select ename, sal from emp where sal not in (800,3000,4000);

ENAME             SAL
---------- ----------
ALLEN            1600
WARD             1250
JONES            2975
MARTIN          1250
BLAKE             2850
CLARK            2450
KING              5000
TURNER          1500
ADAMS           1100
JAMES             950
MILLER           1300

已选择11行。

8.1.5 多行多列子查询,子查询返回多列结果集,有成对比较、非成对比较两种形式。

测试准备

SQL>create table emp1 as select * from emp;
SQL>update emp1 set sal=1600,comm=300 where ename='SMITH';        //SMITH是20部门的员工
SQL>update emp1 set sal=1500,comm=300 where ename='CLARK';          //CLARK是10部门的员工
SQL> select * from emp1;

     EMPNO ENAME      JOB              MGR HIREDATE                   SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
      7369 SMITH      CLERK                7902 1980-12-17 00:00:00       1600        300         20
      7499 ALLEN      SALESMAN        7698 1981-02-20 00:00:00       1600        300         30
      7521 WARD       SALESMAN       7698 1981-02-22 00:00:00       1250        500         30
      7566 JONES      MANAGER         7839 1981-04-02 00:00:00       2975                       20
      7654 MARTIN     SALESMAN      7698 1981-09-28 00:00:00       1250       1400        30
      7698 BLAKE      MANAGER         7839 1981-05-01 00:00:00       2850                        30
      7782 CLARK      MANAGER         7839 1981-06-09 00:00:00       1500        300         10
      7788 SCOTT      ANALYST           7566 1987-04-19 00:00:00       3000                       20
      7839 KING       PRESIDENT                   1981-11-17 00:00:00       5000                      10
      7844 TURNER     SALESMAN      7698 1981-09-08 00:00:00       1500          0           30
      7876 ADAMS      CLERK              7788 1987-05-23 00:00:00       1100                       20
      7900 JAMES      CLERK                7698 1981-12-03 00:00:00        950                       30
      7902 FORD       ANALYST           7566 1981-12-03 00:00:00       3000                       20
      7934 MILLER     CLERK                7782 1982-01-23 00:00:00       1300                      10

已选择14行。

查询条件:查找emp1表中是否有与30部门的员工工资和奖金相同的其他部门的员工。
(注意看一下:现在20部门的SIMTH符合这个条件,它与30部门的ALLEN 有相同的工资和奖金)


成对多列子查询:

特点是主查询每一行中的列都要与子查询返回列表中的相应列同时进行比较,只有各列完全匹配时才显示主查询中的该数据行。

分解一下:

第一步,我们可以先找出emp1表中30号部门的工资和奖金的结果集,(此例没有对comm的空值进行处理)

SQL> select sal,comm from emp1 where deptno=30;

       SAL       COMM
---------- ----------
      1600        300
      1250        500
      1250       1400
      2850
      1500          0
       950

已选择6行。

第二步,列出emp1表中属于这个结果集的所有员工。

SQL> select * from emp1 where (sal,comm) in (select sal,comm from emp1 where deptno=30);

     EMPNO ENAME      JOB              MGR HIREDATE                   SAL       COMM     DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
      7499 ALLEN      SALESMAN        7698 1981-02-20 00:00:00       1600        300         30
      7369 SMITH      CLERK                7902 1980-12-17 00:00:00       1600        300         20
      7521 WARD       SALESMAN        7698 1981-02-22 00:00:00       1250        500         30
      7654 MARTIN     SALESMAN        7698 1981-09-28 00:00:00       1250       1400       30
      7844 TURNER     SALESMAN        7698 1981-09-08 00:00:00       1500          0         30

可以这样想:相当于谓词in(...)中有上面这6行内容,所以上面句子相当于:
select * from emp1 where (sal,comm) in(
(1600,300),(1250,500),(1250,1400),(2850,null),(1500,0),(950,null));

第三步, 再去掉30号部门后,就显示出了在emp1表中与30部门中任意一个员工的工资和奖金完全相同的,但不是30部门的那些员工的信息。

SQL>
select ename,deptno,sal,comm from emp1
  where (sal,comm) in (select sal,comm from emp1 where deptno=30)
and deptno<>30
/


ENAME          DEPTNO        SAL       COMM
---------- ---------- ---------- ----------
SMITH              20            1600        300


考点:1)成对比较是不能使用>any或>all等多行单列比较符的。2)成对比较时的多列顺序和类型必须一一对应。

8.1.6 与非成对比较(含布尔运算)的区别

例:非成对比较

SQL>select ename,deptno,sal,comm
from emp1
where sal in(
        select sal
        from emp1
        where deptno=30)
and
        nvl(comm,0) in (
        select nvl(comm,0)
        from emp1
        where deptno=30)
and deptno<>30
/


ENAME          DEPTNO        SAL       COMM
---------- ---------- ---------- ----------
SMITH              20       1600        300
CLARK              10       1500        300

两个子查询返回的值分别与主查询中的sal和comm列比较,
如果员工的工资与30部门任意一个员工相同,同时,奖金也与30部门的其他员工相同,那么得到了两个员工的信息。
可见,成对比较(使用where (列,列))比非成对比较(使用where 列 and 列) 更为严苛。


8.1.7 关于布尔运算符not

 not 就是否定后面的比较符,基本的形式如下

where empno=7788            where NOT (empno=7788)
where ename LIKE 'S%'             where ename NOT LIKE 'S%'
where deptno IN (20,30)            where deptno NOT IN (20,30)
where sal BETWEEN 1500 AND 3000          where sal NOT BETWEEN 1500 AND 3000
where comm IS NULL            where comm IS NOT NULL
where EXISTS (select子查询)            where NOT EXISTS (select子查询)

8.1.8 not in 在子查询中的空值问题:

"in"与"not in"遇到空值时情况不同,对于"not in" 如果子查询的结果集中有空值,那么主查询得到的结果集也是空。

查找出没有下属的员工,即普通员工,(该员工号不在mgr之列的)

SQL>select ename from emp where empno not in (select mgr from emp);

no rows selected        

上面的结果不出所料,主查询没有返回记录。这个原因是在子查询中有一个空值,而对于not in这种形式,一旦子查询出现了空值,则主查询记录结果也就返回空了。

注意:not后不能跟单行比较符,只有not in组合,没有not any 和not all的组合,但not后可以接表达式 如:

where empno not in(...)与where not empno in(...)两个写法都是同样结果,前者是not in组合,后者是not一个表达式。

例:排除空值的影响

SQL>select ename from emp where empno not in (select nvl(mgr,0)from emp);


8.1.9 from子句中使用子查询(也叫内联视图)

例:员工的工资大于他所在的部门的平均工资的话,显示其信息。

分两步来考虑:

第一步,先看看每个部门的平均工资,再把这个结果集作为一个内联视图。

SQL> select deptno,avg(sal) salavg from emp group by deptno;

    DEPTNO     SALAVG
---------- ----------
        30 1566.66667
        20       2175
        10 2916.66667


第二步,把这个内联视图起一个别名b, 然后和emp 别名e 做连接,满足条件即可。
SQL>
select e.ename, e.sal, e.deptno, b.salavg
from emp e, (select deptno,avg(sal) salavg from emp group by deptno) b
where e.deptno=b.deptno and e.sal > b.salavg
/

ENAME             SAL     DEPTNO     SALAVG
---------- ---------- ---------- ----------
ALLEN            1600         30     1566.66667
JONES            2975         20       2175
BLAKE            2850         30     1566.66667
SCOTT            3000         20       2175
KING             5000         10     2916.66667
FORD             3000         20       2175


8.2关联子查询

其子查询(内部,inner)会引用主查询(外部,outer)查询中的一列或多列。在执行时,外部查询的每一行都被一次一行地传递给子查询,子查询依次读取外部查询传递来的每一值,并将其用到子查询上,直到外部查询所有的行都处理完为止,最后返回查询结果。

理论上主查询有n行,子查询被调用n次。

例1,关联查询用于select语句

8.1.9小节的例子,显示员工的工资大于他所在部门的平均工资,也可以使用关联查询。

SQL> select ename,sal,deptno from emp outer where sal> (select avg(sal) from emp inner where inner.deptno=outer.deptno);

ENAME             SAL     DEPTNO
---------- ---------- ----------
ALLEN            1600         30
JONES            2975         20
BLAKE            2850         30
SCOTT            3000         20
KING             5000         10
FORD             3000         20

例2,关联查询用于update语句

SQL> create table emp1 as (select e.empno,e.ename,d.loc,d.deptno from emp e,dept d where e.deptno=d.deptno(+));
SQL> update emp1 set loc=null;
SQL> commit;

如何通过关联查询再将emp1表更新回原值。

SQL> update emp1 e set loc=(select d.loc from dept d where e.deptno=d.deptno);

例3. 关联查询中的特殊形式,使用EXISTS或NOT EXISTS

EXISTS关心的是在子查询里能否找到一个行值(哪怕有10行匹配,只要找到一行就行),如果子查询有行值,则立即停止子查询的搜索,然后返回逻辑标识TRUE, 如果子查询没有返回行值,则返回逻辑标识FALSE, 子查询要么返回T,要么返回F,以此决定了主查询的调用行的去留,然后主查询指针指向下一行,继续调用子查询...

EXISTS的例子:显示出emp表中那些员工不是普通员工(属于大小领导的)。

SQL> select empno,ename,job,deptno from emp outer where exists (select 'X' from emp where mgr=outer.empno);

     EMPNO ENAME      JOB           DEPTNO
---------- ---------- --------- ----------
      7566 JONES      MANAGER           20
      7698 BLAKE      MANAGER           30
      7782 CLARK      MANAGER           10
      7788 SCOTT      ANALYST             20
      7839 KING       PRESIDENT           10
      7902 FORD       ANALYST             20

已选择6行。

说明:exists子查询中select 后的‘X'只是一个占位,它返回什么值无关紧要,它关心的是子查询中否‘存在’,即子查询的where条件能否有‘结果’,一旦子查询查到一条记录满足where条件,则立即返回逻辑‘TRUE’,(就不往下查了)。否则返回‘FALSE’。

NOT EXISTS的例子:显示dept表中还没有员工的部门。

SQL> select deptno,dname from dept d where not exists (select 'X' from emp where deptno=d.deptno);

    DEPTNO DNAME
---------- --------------
        40 OPERATIONS


对于关联子查询,在某种特定的条件下,比如子查询是个大表,且连接字段建立了索引,那么使用exists比in的效率可能更高。

8.10 关于别名的使用

有表别名和列别名, 表别名用于多表连接或子查询中,列别名用于列的命名规范。

如果别名的字面值有特殊字符,需要使用双引号。如:"AB C"

8.3 必须使用别名的地方:

1)两表连接后,select 投影中有相同命名的列,必须使用表别名区别标识(自然连接中的公共列则使用相反原则)

select ename,d.deptno from emp e,dept d where e.deptno=d.deptno;

2)使用create * {table |view} as select ...语句创建一个新的对象,其字段名要符合对象中字段的规范,不能是表达式或函数等非规范字符,而使用别名可以解决这个问题。

create table emp1 as select deptno,avg(sal) salavg from emp group by deptno;
create view v as select deptno,avg(sal) salavg from emp group by deptno;

3)使用内联视图时, 若where子句还要引用其select中函数的投影, 使用别名可以派上用场。

select * from (select avg(sal) salavg from emp) where salavg>2000;

4)当以内联视图作为多表连接,主查询投影列在形式上不允许单行字段(或函数)与聚合函数并列,解决这个问题是使在内联视图中为聚合函数加别名,然后主查询的投影中引用其别名。

select e.ename,e.sal,b.deptno,b.salavg
       from emp e,(select deptno,avg(sal) salavg from emp group by deptno) b
       where e.deptno=b.deptno;

5)rownum列是Oracle的伪列,加别名可以使它成为一个表列,这样才可以符合SQL99标准中的连接和选择。

SQL> select * from (select ename,rownum rn from emp) where rn>5;

6)不能使用别名的地方:

在一个独立的select结构的投影中使用了列别名,不能在其后的where 或having中直接引用该列别名(想想为什么?)。

select ename,sal salary from emp where salary>2000;                --错

select deptno,avg(sal) salavg from emp group by deptno having salavg>2000; --错


8.4 简单查询与复杂查询练习题:

1)列出emp表工资最高的前三名员工信息

select * from (select * from emp order by sal desc) where rownum < 4;  

关于rownum 伪列使用特别需要注意两点:

1,rownum>时不会返回任何行
2,rownum< 和and并用时,是在另一个条件基础上的rownum<  ,而不是两个独立条件的并集(intersect)

体会一下:
SQL> select ename,sal,deptno from emp where deptno=10;

ENAME             SAL     DEPTNO
---------- ---------- ----------
CLARK            2450         10
KING              5000         10
MILLER           1300         10

SQL> select ename,sal,deptno from emp where rownum <=1;

ENAME             SAL     DEPTNO
---------- ---------- ----------
SMITH             800         20

SQL> select ename,sal,deptno from emp where rownum <=1 and deptno=10;

ENAME             SAL     DEPTNO
---------- ---------- ----------
CLARK            2450         10

//即在deptno=10的基础上再rownum<=1

2) 列出emp表第5-10名员工(按sal大--小排序)的信息(结果集的分页查询技术)

SQL> select * from (select t1.*, rownum rn from (select * from emp order by sal desc) t1) where rn between 5 and 10;
 
EMPNO ENAME      JOB         MGR HIREDATE          SAL      COMM DEPTNO         RN
----- ---------- --------- ----- ----------- --------- --------- ------ ----------
 7698 BLAKE      MANAGER    7839 1981-5-1      2850.00                   30              5
 7782 CLARK      MANAGER    7839 1981-6-9      2450.00                   10              6
 7499 ALLEN      SALESMAN   7698 1981-2-20     1600.00    3 00.00     30              7
 7844 TURNER   SALESMAN   7698 1981-9-8      1500.00          0.00     30              8
 7934 MILLER     CLERK          7782 1982-1-23     1300.00                   10               9
 7521 WARD      SALESMAN   7698 1981-2-22     1250.00    500.00     30             10
 
6 rows selected

3)从列出emp表中显示员工和经理对应关系表。(emp自连,利用笛卡尔积)

select a.empno,a.ename,a.mgr,b.empno,b.ename,b.mgr
from emp a, emp b
where a.mgr=b.empno;


4)要求列出emp表中最高工资的员工所在工作地点。(emp+dept左外,如果该员工不属于任何部门则列出员工姓名)

select a.ename, d.loc from
(select * from emp where sal=
(select max(sal) from emp)) a left join dept d on a.deptno=d.deptno ;


5)CTAS方法建立dept1,将dept1表增加一列person_count,要求根据emp表填写dept1表的各部门员工合计数(典型的关联查询)。

SQL> create table dept1 as select * from dept;
SQL> alter table dept1 add person_count int;
SQL> update dept1 d set person_count=(select count(*) from emp e where e.deptno=d.deptno);
SQL> select * from dept1;

    DEPTNO DNAME          LOC           PERSON_COUNT
---------- -------------- ------------- ------------
        10 ACCOUNTING     NEW YORK                3
        20 RESEARCH           DALLAS                     5
        30 SALES                  CHICAGO                  6
        40 OPERATIONS      BOSTON               0


6) 复杂select查询,以HR用户的几个表为例(PPT-II-8),显示欧洲地区员工的平均工资及人数(使用多表连接及内联视图)
SQL>
select avg(salary),count(salary) from
  (select e.first_name,e.salary,d.department_id,l.location_id, c.country_id, r.region_id,r.region_name
          from employees e,departments d,locations l,countries c,regions r
            where e.department_id=d.department_id and d.location_id=l.location_id
              and l.country_id=c.country_id and c.region_id=r.region_id and r.region_name='Europe');

AVG(SALARY) COUNT(SALARY)
----------- -------------
 8916.66667            36


7)同上题(使用嵌套子查询技术)

SQL>
select avg(salary),count(*) from employees where department_id in
(select department_id from departments where location_id in
 (select location_id from locations where country_id in
   (select country_id from countries where region_id=
    (select region_id from regions where region_name='Europe')
   )
 )
);

AVG(SALARY) COUNT(SALARY)
----------- -------------
 8916.66667            36

分解如下:

SQL> select region_id from regions where region_name='Europe';

 REGION_ID
----------
         1

SQL> select country_id from countries where region_id=1;

CO
--
BE
CH
DE
DK
FR
IT
NL
UK

SQL> select location_id from locations where country_id in ('BE','CH','DE','DK','FR','IT','NL','UK');

LOCATION_ID
-----------
       1000
       1100
       2400
       2500
       2600
       2700
       2900
       3000
       3100


SQL> select department_id from departments where location_id in (1000,1100,2400,2500,2600,2700,2900,3000,3100);

DEPARTMENT_ID
-------------
           40
           70
           80

SQL> select avg(salary),count(*) from employees where department_id in(40,70,80);

AVG(SALARY)   COUNT(*)
----------- ----------
 8916.66667         36


posted on 2018-06-11 10:25  小孩没穿鞋  阅读(1390)  评论(0编辑  收藏  举报

导航