級聯查詢(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)

相關文章
相關標籤/搜索