1. 程式人生 > >用 SQL 查詢/構建樹型(層次)資料

用 SQL 查詢/構建樹型(層次)資料

<?XML:NAMESPACE PREFIX = O />

什麼是層次/樹型是什麼概念?

If a table contains hierarchical data, then you can select rows in a hierarchical.

注:在Oracle裡稱為hierarchical queries,而為了方便理解,把它譯為層次/樹型查詢。

作用 & 特徵

通常用於查詢整個層次/樹型關係的資料。如:公司的架構體系裡面,表裡儲存了每一層職位的資訊,需要全部查出來;(樹的形式)一些順序型的記錄,像:A群由上海搬到北京,之後又搬到廣州,不久又搬到南京。(單鏈表形式)

凡是在表裡存在以上形式的資料,都可以根據一個點,它所有關聯的資料通過一條SELECT語句一次過查詢出來。

示例

網上有太多的層次/樹型結構的例子,而且在Oracle官方文件裡也有很好的例子。因此本文從資料結構的角度去說明一下具體的用法,不涉及任何業務知識。

先看一張關係圖:

大小: 4.59 K 尺寸: 258 x 200 瀏覽: 18 次 點選開啟新視窗瀏覽全圖

至於上圖的樹型,我們通常說成:1 是根節點;4的父節點是2,子節點有56;而像356…這些沒有子節點的節點稱為葉結點。(不同的描述有不同的說法,在文章繼續之前先統一一下說法)

用表把上面的圖存起來。

SQL程式碼
  1. CREATETABLE t_tree(fnum NUMBER, fparent NUMBER);    
  2. INSERTINTO t_tree(fnum, fparent) VALUES(1, null);     
  3. INSERTINTO t_tree(fnum, fparent) VALUES(2, 1);    
  4. INSERTINTO t_tree(fnum, fparent) VALUES(3, 2);     
  5. INSERTINTO t_tree(fnum, fparent) VALUES(4, 2);     
  6. INSERTINTO t_tree(fnum, fparent) VALUES(5, 4);    
  7. INSERTINTO t_tree(fnum, fparent) 
    VALUES(6, 4);    
  8. INSERTINTO t_tree(fnum, fparent) VALUES(7, 1);     
  9. INSERTINTO t_tree(fnum, fparent) VALUES(8, 7);    
  10. INSERTINTO t_tree(fnum, fparent) VALUES(9, 1);   
  11. INSERTINTO t_tree(fnum, fparent) VALUES(10, 9);    
  12. INSERTINTO t_tree(fnum, fparent) VALUES(11, 10);    
  13. INSERTINTO t_tree(fnum, fparent) VALUES(12, 9);    
  14. COMMIT;   

根據需求寫SQL:

查出 1 下面的所有成員:(順推)

SQL程式碼
  1. SELECT fparent  -- 父節點  
  2.        ,fnum    -- 子節點
  3.        ,LEVELAS n_level  -- 第幾層(邏輯)
  4.        ,sys_connect_by_path(fnum, '/'AS path -- 整個路徑
  5.        ,connect_by_isLeaf AS"isLeaf"-- 是否為葉節點(1 - 是;0 - 否)
  6. FROM t_tree   
  7.   START WITH fparent = 1   
  8. CONNECTBYPRIOR fnum = fparent  
結果
  1. FPARENT       FNUM    N_LEVEL PATH             isLeaf   
  2. ------- ---------- ---------- ------------   ----------
  3.       1          2          1 /2                 0   
  4.       2          3          2 /2/3               1   
  5.       2          4          2 /2/4               0   
  6.       4          5          3 /2/4/5             1   
  7.       4          6          3 /2/4/6             1   
  8.       1          7          1 /7                 0   
  9.       7          8          2 /7/8               1   
  10.       1          9          1 /9                 0   
  11.       9         10          2 /9/10              0   
  12.      10         11          3 /9/10/11           1   
  13.       9         12          2 /9/12              1   
  14. rows selected   

查出 8 上面的所有成員:(逆推)

SQL程式碼
  1. SELECT fparent -- 父節點   
  2.        ,fnum   -- 子節點   
  3.        ,LEVELAS n_level -- -- 第幾層(邏輯)   
  4.        ,sys_connect_by_path(fnum, '/'AS path -- 整個路徑   
  5.        ,connect_by_isLeaf AS"isLeaf"-- 是否為葉節點(1 - 是;0 - 否)   
  6. FROM t_tree      
  7.  START WITH fnum = 8      
  8. CONNECTBY fnum = PRIOR fparent    
結果
  1. FPARENT       FNUM    N_LEVEL      PATH           isLeaf      
  2. --------- ---------- ---------- ------------- ----------   
  3.    7          8        1            /8               0      
  4.    1          7        2            /8/7             0      
  5.               1        3            /8/7/1           1      

查出所有葉(Leaf)結點,沒有子節點的節點:

SQL程式碼
  1. SELECT *   
  2. FROM t_tree tr   
  3. WHERE 0 = (SELECTCOUNT(1)   
  4. FROM t_tree   
  5. WHERE fparent = tr.fnum)   
  6. ORDERBY fnum  
結果
  1. FNUM    FPARENT   
  2. ---- ----------
  3.    3          2   
  4.    5          4   
  5.    6          4   
  6.    8          7   
  7.   11         10   
  8.   12          9   

其它說明

調優

內部不斷根據子、父節點來查詢本表。資料量大的話,給表新增子、父節點兩個欄位的索引,會有很明顯的快速效果。

根節點

Oracle 提供了可以快速找到根節點的操作符,如下SQL

SQL程式碼
  1. SELECT fparent  -- 父節點  
  2.        ,fnum    -- 子節點
  3.        ,LEVELAS n_level  -- 第幾層(邏輯)
  4.        ,sys_connect_by_path(fnum, '/'AS path -- 整個路徑
  5.        ,connect_by_root fparent AS root_top -- 根節點
  6. FROM t_tree   
  7.   START WITH fparent = 9   
  8. CONNECTBYPRIOR fnum = fparent  
結果
  1. FPARENT       FNUM    N_LEVEL PATH               ROOT_TOP   
  2. ------- ---------- ---------- ----------------- ----------
  3.       9         10          1 /10                  9   
  4.      10         11          2 /10/11               9   
  5.       9         12          1 /12                  9   

如果指定一個節點開始查的話,ROOT就是指定的那個節點,直接用 START WITH 後面那個值就行。不過如果沒有指定一個節點,表裡有多棵樹,這個就很有用了。

迴圈關係

在記錄裡有迴圈關係,樹型有根和子節點,而沒有迴圈的關係。這裡簡單說明一下,迴圈是怎回事吧。如新增一條這樣的記錄:

SQL程式碼
  1. INSERTINTO t_tree(fnum, fparent) VALUES(7, 8);  

<?XML:NAMESPACE PREFIX = ST1 />1-7-8這條路徑製造一條迴圈記錄。之後從8開始逆推到上一層(會報錯):

SQL程式碼
  1. SQL> SELECT fparent -- 父節點
  2.   2         ,fnum   -- 子節點
  3.   3         ,LEVELAS n_level -- -- 第幾層(邏輯)
  4.   4         ,sys_connect_by_path(fnum, '/'AS path -- 整個路徑
  5.   5         ,connect_by_isLeaf AS"isLeaf"-- 是否為葉節點(1 - 是;0 - 否)
  6.   6    FROM t_tree   
  7.   7   START WITH fnum = 8   
  8.   8   CONNECTBY fnum = PRIOR fparent   
  9.   9  /   
  10. SELECT fparent -- 父節點
  11.        ,fnum   -- 子節點
  12.        ,LEVELAS n_level -- -- 第幾層(邏輯)
  13.        ,sys_connect_by_path(fnum, '/'AS path -- 整個路徑
  14.        ,connect_by_isLeaf AS"isLeaf"-- 是否為葉節點(1 - 是;0 - 否)
  15. FROM t_tree   
  16.  START WITH fnum = 8   
  17. CONNECTBY fnum = PRIOR fparent   
  18. ORA-01436: 使用者資料中的 CONNECTBY 迴圈   
  19. SQL>   

這裡需要用到一個迴圈處理,如下:

SQL程式碼
  1. SQL> SELECT fparent -- 父節點
  2.   2         ,fnum   -- 子節點
  3.   3         ,LEVELAS n_level -- -- 第幾層(邏輯)
  4.   4         ,sys_connect_by_path(fnum, '/'AS path -- 整個路徑
  5.   5         ,connect_by_isLeaf AS"isLeaf"-- 是否為葉節點(1 - 是;0 - 否)
  6.   6         ,connect_by_isCycle AS"isCycle"-- 是否為迴圈節點(1 - 是;0 - 否)
  7.   7    FROM t_tree   
  8.   8   START WITH fnum = 8   
  9.   9   CONNECTBY NOCYCLE fnum = PRIOR fparent   
  10.  10  /   
  11.    FPARENT       FNUM    N_LEVEL PATH                 isLeaf    isCycle   
  12. ---------- ---------- ---------- ------------------ ---------- ----------
  13.          7          8          1 /8                     0          0   
  14.          1          7          2 /8/7                   0          0   
  15.                     1          3 /8/7/1                 1          0   
  16.          8          7          2 /8/7                   1          1