级联查询(Hierarchical Queries) 进阶应用:伪列LeveL

1、使用伪列Level显示表中节点的层次关系:

Oracle9i对级联查询的支持不只在于提供了像Start with...Connect by这样的子句供咱们很方便地执行查询,并且还提供了一个伪列(Pseudocolumn): Level。这个伪列的做用是在递归查询的结果中用来表示节点在整个结构中所处的层次
sql

下面咱们来看看实际的例子:
仍是上次那个employee表,如今咱们要在上次的需求上面增长点小玩意:输出每一个节点的层次值,看以下SQL:
函数

SQL> select level, id, emp_name, manager_id from employee 
     start with id = 2 connect by prior id = manager_id order by id;

     LEVEL         ID EMP_NAME             MANAGER_ID
---------- ---------- -------------------- ----------
         1          2 mark                          1
         2          4 tom                           2
         2          5 paul                          2
         3          7 ben                           4

SQL>

咱们能够看到在LEVEL列,输出了1,2,2,3的值,这就是Oracle为咱们提供的一个伪列。此伪列只能用在start with...connect by子句中,下面咱们来看另外一种方式是否可行:spa

SQL> select level, p.* from (select * from employee start with id = 2 
     connect by prior id = manager_id order by id) p;

     LEVEL         ID EMP_NAME             MANAGER_ID
---------- ---------- -------------------- ----------
         0          2 mark                          1
         0          4 tom                           2
         0          5 paul                          2
         0          7 ben                           4

SQL>



能够看到Level列的值所有变成了0,可见在这里Oracle并不认为虚表P里面的数据是“层次关系”,于是对于Level都返回0

2、统计表中节点的层数:

code

假设如今咱们想看一下当前employee表中员工总共分为几个级别,咱们应该如何作呢?请看下面的SQL递归

SQL> select * from employee;

        ID EMP_NAME             MANAGER_ID
---------- -------------------- ----------
         1 king
         2 mark                          1
         3 bob                           1
         4 tom                           2
         5 paul                          2
         6 jack                          3
         7 ben                           4

7 rows selected.

SQL> 
SQL> 
SQL> select count(level) from employee start with manager_id is null 
     connect by prior id = manager_id;

COUNT(LEVEL)
------------
           7

SQL> 
SQL> select count(distinct level) from employee start with manager_id is null 
     connect by prior id = manager_id;

COUNT(DISTINCTLEVEL)
--------------------
           4


从这里咱们能够看到,在统计的时候必定要使用distinct关键字,不然获得的错误的结果。

3、统计表中各个层次的节点数量:

假设咱们想知道employee表中每一个级别的员工数量,咱们应该如何作呢--对了,使用Level和group by子句了ci

SQL> select level, count(level) from employee start with manager_id is null 
     connect by prior id = manager_id group by level;

     LEVEL COUNT(LEVEL)
---------- ------------
         1            1
         2            2
         3            3
         4            1


4、查找表中各个层次的节点信息:

上面的例子很简单,咱们看到Level能够用在group by子句中,如今咱们更进一步,查看指定层次的员工信息,好比说我如今打算查看Level=2的全部员工的记录,应该如何作呢?很天然地咱们想到了第一个SQL语句:get

SQL> select level, id, emp_name, manager_id from employee where level >= 2;

no rows selected

很奇怪吧,这这里level关键字就不起做用了,这是由于level伪列只能在和start with...connect by子句结合时才能发挥做用,就想上面的统计各层节点数量同样,因而咱们又立马想到了第二个SQL语句:it

select *
  from (select level, id, emp_name, manager_id
          from employee
          start with manager_id is null
          connect by prior id = manager_id
          order by id) p
 where p.level = 2

看起来这个句子没有什么问题吧,实际执行的效果如何呢?咱们在SQL*PLUS下执行,结果倒是报错:io

ERROR at line 1:
ORA-01747: invalid user.table.column, table.column, or column specification

很郁闷!为何会报p.level不可识别呢?这是由于level是Oracle的伪列,并不属于任何一个表,咱们必须使用别名把这个伪列“假装”成一个实际的列,如今咱们看第三个语句,注意语句高亮处。table


此次终于搞定了!不过实际上咱们有更简单的解决方法,请看第四个SQL语句:

SQL> select level, id, emp_name, manager_id
      from employee
      where level = 2
      start with manager_id is null
      connect by prior id = manager_id
      order by id;

     LEVEL         ID EMP_NAME             MANAGER_ID
---------- ---------- -------------------- ----------
         2          2 mark                          1
         2          3 bob                           1


上面咱们是查看某个层次的全部节点信息,如今咱们打算看看全部层次的节点信息,并且要求用一种直观的信息显示出来。下面的例子演示了如何使用空格缩进的方式来直观显示节点之间的层次关系:

SQL> select level, id, lpad('  ', 2 * (level - 1)) || emp_name name, manager_id
      from employee
      start with manager_id is null
      connect by prior id = manager_id;

     LEVEL         ID NAME                 MANAGER_ID
---------- ---------- -------------------- ----------
         1          1 king
         2          2   mark                        1
         3          4     tom                       2
         4          7       ben                     4
         3          5     paul                      2
         2          3   bob                         1
         3          6     jack                      3

7 rows selected.


请注意这里的lpad函数的做用,正是它利用了层次和空格进行缩进,让咱们能够很直观地从NAME字段对齐方式就知道各个节点的层次关系。若是咱们须要过滤其中的某些节点,只须要将where条件加在start with前面就能够了(注意必须是前面,不然会报语法错误)。

5、在Start with中使用子查询:

在前面咱们看到的例子中,start with的值都是一个固定的内容,但有些时候查询的起始点并不容易肯定,好比:查询工号最小的员工节点及其子节点,这个时候工号最小很明显是一个查询的条件,须要咱们先经过执行一个查询获得肯定的值,再做为查询的起点。请看例子:

SQL>  select level, id, lpad('  ', 2 * (level - 1)) || emp_name name, manager_id
      from employee
      start with id = (select min(id) from employee)
      connect by prior id = manager_id;

     LEVEL         ID NAME                 MANAGER_ID
---------- ---------- -------------------- ----------
         1          1 king
         2          2   mark                        1
         3          4     tom                       2
         4          7       ben                     4
         3          5     paul                      2
         2          3   bob                         1
         3          6     jack                      3

7 rows selected.


6、判断节点和节点之间是否具备层次关系:

在平常工做中除了查询节点的信息以外,另外一个常见的应用就是判断某个节点和另一个/些节点之间是否具备层次关系。例如我想知道员工mark是否是员工jack的领导(直接或间接的均可以),我应该怎么作呢?

考虑到start with...connect by会返回一棵节点树,假如节点数上没有jack节点,那么说明mark并非jack的直接或间接领导,若是找到那说明mark是jack的父节点。方法简单

SQL> select level,
           id,
           lpad('  ', 2 * (level - 1)) || emp_name employee_name,
           manager_id
     from employee
     where emp_name = 'jack'
     start with emp_name = 'mark'
     connect by prior id = manager_id;

no rows selected


7、删除级联表中的子树:

假设如今employee表中的mark及其下属员工离职,那么咱们为了维护数据的完整性,必须将mark及其下属员工的节点都删除,有了start with...connect by和level咱们就能够轻松地作到这一点了。

【1】按名称删除节点树:

SQL> delete from employee
     where id in (select id
                    from employee
                    start with emp_name = 'mark'
                    connect by prior id = manager_id);

4 rows deleted.

【2】按层次删除节点树:

从上面的例子咱们知道只须要在第一个SQL的基础上改变一下:使用level区分节点的层次就作到了。

参考资料:《Mastering Oracle SQL》(By Alan Beaulieu, Sanjay Mishra O'Reilly June 2004  0-596-00632-2)

相关文章
相关标签/搜索