Oracle層次查詢及應用(start with connect by)


摘要:本文將根據對層次查詢語句簡單例子的說明來理解應用,並舉例實際的應用案例。
========================================================================================================
start with connect by 層次查詢(Hierarchical Queries)
========================================================================================================
語法
--------------------------------------------------------------------------------------------------------
SELECT *
  FROM table
 WHERE 
 START WITH 
 CONNECT BY 
 ORDER BY col1, col2 ...

SELECT     *
      FROM table
START WITH ID = 1
CONNECT BY PRIOR PID = ID

start with: 表示根記錄的條件
connect by: 指定了父記錄行和子記錄行之間的關系,在層次查詢中,條件表達式必須使用prior操作符來指定父記錄行
如:
CONNECT BY PRIOR pid = id 或者CONNECT BY pid = PRIOR id
如果connect by 條件是一個組合條件,那么只有一個條件需要prior操作符,
如:
CONNECT BY last_name != 'King' AND PRIOR employee_id = manager_id

不過,connect by 不能包含子查詢。
prior是一個二元操作符,最常見的是用於列值相等的比較,它讓Oracle使用對應列的父親行的值。使用非相等比較,極有可能倒致查詢陷入無窮循環,以出錯終止。

舉例
========================================================================================================
Start with...Connect By子句遞歸查詢一般用於一個表維護樹形結構的應用。可以通過一個簡單的例子來理解其使用的概念和方法。
創建示例表:
--------------------------------------------------------------------------------------------------------

View Code
1 CREATE TABLE tbl_test
2 (
3 ID NUMBER,
4 NAME VARCHAR2(10),
5 pid NUMBER DEFAULT 0
6 );



 
插入測試數據:
--------------------------------------------------------------------------------------------------------
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('1','111','0');
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('2','222','1');
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('3','333','0');
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('4','444','1');
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('5','555','2');
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('6','666','0');
INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('9','999','2');
 
全部記錄
--------------------------------------------------------------------------------------------------------
SELECT * FROM tbl_test
如下記錄
--------------------
ID NAME PID
1 111 0 
2 222 1 
3 333 0 
4 444 1 
5 555 2 
6 666 0 
9 999 2 

從父記錄行向子記錄行遞歸
--------------------------------------------------------------------------------------------------------

View Code
1 SELECT     *
2 FROM tbl_test
3 START WITH ID = 1
4 CONNECT BY PRIOR ID = pid



如下記錄
--------------------
ID NAME PID
1 111 0 
2 222 1 
5 555 2 
9 999 2 
4 444 1 
--------------------
解析
1.(START WITH ID = 1)根記錄條件為ID=1
2.(CONNECT BY PRIOR ID = pid):由列ID與PID建立父子關系並進行比較,從ID為1開始,在PID列中尋找為1的行,可以找到ID為2和4,再將ID為2和4從PID中再尋找,又可以找到5和9,以上結果因此而來。
 
從子記錄向父記錄遞歸
--------------------------------------------------------------------------------------------------------

View Code
1 SELECT     *
2 FROM tbl_test
3 START WITH ID = 5
4 CONNECT BY PRIOR pid = ID



如下記錄
--------------------
ID NAME PID
5 555 2 
2 222 1 
1 111 0 
--------------------
解析
1.(START WITH ID = 5)根記錄條件為ID=5
2.(CONNECT BY PRIOR pid = ID):以之上查詢恰相反,其中的取值也正相反。ID為5的PID列的值為2,因PRIOR在PID列一邊,確從PID列中取值,在PID列取值2向ID列進行遞歸查詢,在ID列中找到2的值,再確認其對應的PID為1,再次取值1在ID中找到結果,最終共計三條記錄。

========================================================================================================
LEVEL,ROW_NUMBER,OVER的應用
========================================================================================================
設PID為父值,並根據PID進行分組及確定LEVEL
--------------------------------------------------------------------------------------------------------

View Code
1 SELECT     LEVEL, pid,
2 ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid) by_pid,
3 ROW_NUMBER () OVER (ORDER BY pid) AS rn, NAME
4 FROM tbl_test
5 START WITH pid = 0
6 CONNECT BY PRIOR ID = pid
7 ORDER BY 1



如下記錄
--------------------------------------------------------------------------------------------------------
LEVEL PID BY_PID RN NAME
1 0 1 1 111 
1 0 2 2 333 
1 0 3 3 666 
2 1 1 4 222 
2 1 2 5 444 
3 2 1 6 555 
3 2 2 7 999 

根據父值逐層區分
--------------------------------------------------------------------------------------------------------

View Code
 1 SELECT     pid, SYS_CONNECT_BY_PATH (by_path, ',')
2 FROM (SELECT pid,
3 ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid)
4 by_pid,
5 ROW_NUMBER () OVER (ORDER BY pid) + pid AS rn,
6 NAME AS by_path
7 FROM tbl_test
8 ORDER BY 1)
9 START WITH by_pid = 1
10 CONNECT BY rn - 1 = PRIOR rn
11 ORDER BY 1



記錄如下:
--------------------------------------------------------------------------------------------------------
PID ID BS_NAME
0 1 111 
1 2              222 
2 5                            555 
2 9                            999 
1 4              444 
0 3 333 
0 6 666 

可以清楚看出,ID為1,其子值為2和4(level 2),而2值又有子值5、9(level 3),3、6無子值存在。

========================================================================================================
SYS_CONNECT_BY_PATH 函數
========================================================================================================
以上例顯示看出,PID分為三個分支,NAME分別如下:
第一分支:111,333,666
第二分支:222,444
第三分支:555,999

腳本:
--------------------------------------------------------------------------------------------------------

View Code
 1 SELECT     pid, SYS_CONNECT_BY_PATH (by_path, ',')
2 FROM (SELECT pid,
3 ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid)
4 by_pid,
5 ROW_NUMBER () OVER (ORDER BY pid) + pid AS rn,
6 NAME AS by_path
7 FROM tbl_test
8 ORDER BY 1)
9 START WITH by_pid = 1
10 CONNECT BY rn - 1 = PRIOR rn
11 ORDER BY 1



記錄結果:
--------------------------------------------------------------------------------------------------------
PID SYS_CONNECT_BY_PATH(BY_PATH,',')
0 ,111 
0 ,111,333 
0 ,111,333,666 
1 ,222 
1 ,222,444 
2 ,555 
2 ,555,999 

如取單值列,可取其中最大值,使用MAX,然后應用GROUP BY即可,如下腳本:
--------------------------------------------------------------------------------------------------------

View Code
 1 SELECT     pid, LTRIM (MAX (SYS_CONNECT_BY_PATH (by_path, ',')), ',')
2 FROM (SELECT pid,
3 ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid)
4 by_pid,
5 ROW_NUMBER () OVER (ORDER BY pid) + pid AS rn,
6 NAME AS by_path
7 FROM tbl_test
8 ORDER BY 1)
9 START WITH by_pid = 1
10 CONNECT BY rn - 1 = PRIOR rn
11 GROUP BY pid
12 ORDER BY 1



記錄結果:
--------------------------------------------------------------------------------------------------------
PID LTRIM(MAX(SYS_CONNECT_BY_PATH(BY_PATH,',')),',')
0 111,333,666 
1 222,444 
2 555,999 
--------------------------------------------------------------------------------------------------------
常用於行列轉換的應用。


應用:
一、ERP BOM(物料清單)
========================================================================================================

View Code
 1 SELECT DISTINCT b.lvl lv, msi1.segment1 p_item, msi1.description p_item_desc,
2 msi1.primary_uom_code, b.item_num num, b.operation_seq_num,
3 msi2.segment1 c_item, msi2.description c_item_desc,
4 msi2.primary_uom_code, b.component_quantity,
5 b.component_yield_factor,
6 DECODE (b.wip_supply_type,
7 1, 'Push',
8 2, 'Assembly Pull'
9 ) TYPE, b.supply_subinventory, b.planning_factor
10 FROM inv.mtl_system_items_b msi1,
11 inv.mtl_system_items_b msi2,
12 bom.bom_structures_b bom,
13 inv.mtl_parameters mp,
14 (SELECT LEVEL lvl, bic.bill_sequence_id,
15 bic.component_item_id, bic.component_quantity,
16 bic.component_yield_factor, bic.operation_seq_num,
17 bic.item_num, bic.wip_supply_type,
18 bic.supply_subinventory, bic.effectivity_date,
19 bic.planning_factor
20 FROM bom.bom_components_b bic
21 WHERE disable_date IS NULL AND bic.planning_factor > 0
22 START WITH bic.bill_sequence_id IN (
23 SELECT bill_sequence_id
24 FROM bom.bom_structures_b bom2,
25 inv.mtl_system_items_b msi,
26 inv.mtl_parameters mp
27 WHERE bom2.assembly_item_id = msi.inventory_item_id
28 AND bom2.organization_id = msi.organization_id
29 AND msi.segment1 = 'FQH1AU3ACBBH34HD02'
30 AND mp.organization_code = 'ZP1'
31 AND msi.organization_id = mp.organization_id
32 AND bom2.alternate_bom_designator IS NULL)
33 CONNECT BY bic.bill_sequence_id =
34 PRIOR (SELECT DISTINCT bill_sequence_id
35 FROM bom.bom_structures_b bo,
36 inv.mtl_system_items_b msi,
37 inv.mtl_parameters mp
38 WHERE bo.assembly_item_id = bic.component_item_id
39 AND mp.organization_code ='ZP1'
40 AND bo.organization_id = mp.organization_id
41 AND bo.organization_id = msi.organization_id
42 AND bo.assembly_item_id =msi.inventory_item_id
43 AND bo.alternate_bom_designator IS NULL
44 AND disable_date IS NULL)) b
45 WHERE b.bill_sequence_id = bom.bill_sequence_id
46 AND mp.organization_code = 'ZP1'
47 AND bom.organization_id = mp.organization_id
48 AND bom.organization_id = msi1.organization_id
49 AND bom.assembly_item_id = msi1.inventory_item_id
50 AND bom.organization_id = msi2.organization_id
51 AND b.component_item_id = msi2.inventory_item_id



二、行列轉換
========================================================================================================

1 SQL> SELECT deptno, ename FROM emp ORDER BY deptno, ename;



DEPTNO ENAME
--------------------------------------------------------------------------------------------------------
    10 CLARK
    10 KING
    10 MILLER
    20 ADAMS
    20 FORD
    20 JONES
    20 SCOTT
    20 SMITH
    30 ALLEN
    30 BLAKE
    30 JAMES
    30 MARTIN
    30 TURNER
    30 WARD

14 rows selected.
想輸出為:
DEPTNO ENAME
--------------------------------------------------------------------------------------------------------
    10 CLARK, KING, MILLER
    20 ADAMS, FORD, JONES, SCOTT, SMITH
    30 ALLEN, BLAKE, JAMES, MARTIN, TURNER, WARD

除了使用聚集函數或者存儲過程之外(行列轉換 http://erplife.blog.sohu.com/72186257.html),9i中可以:
--------------------------------------------------------------------------------------------------------

View Code
 1 SELECT     deptno,
2 LTRIM
3 (MAX (SYS_CONNECT_BY_PATH (ename, ','))KEEP (DENSE_RANK LAST ORDER BY curr),
4 ','
5 ) AS concatenated
6 FROM (SELECT deptno, ename,
7 ROW_NUMBER () OVER (PARTITION BY deptno ORDER BY ename)
8 AS curr,
9 ROW_NUMBER () OVER (PARTITION BY deptno ORDER BY ename)
10 - 1 AS prev
11 FROM emp)
12 GROUP BY deptno
13 CONNECT BY prev = PRIOR curr AND deptno = PRIOR deptno
14 START WITH curr = 1;



記錄如下:
--------------------------------------------------------------------------------------------------------

View Code
1 DEPTNO CONCATENATED
2 10 CLARK,KING,MILLER
3 20 ADAMS,FORD,JONES,SCOTT,SMITH
4 30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD



三、Oracle 10g偽列函數
========================================================================================================
 我們可以通過START WITH . . . CONNECT BY . . .子句來實現SQL的 層次查詢,而Oracle 10g 為其添加許多了新的偽列。

View Code
 1 create table hier
2 (parent varchar2(30),
3 child varchar2(30)
4 );
5
6 insert into hier values(null,'Asia');
7 insert into hier values(null,'Australia');
8 insert into hier values(null,'Europe');
9 insert into hier values(null,'North America');
10 insert into hier values('Asia','China');
11 insert into hier values('Asia','Japan');
12 insert into hier values('Australia','New South Wales');
13 insert into hier values('New South Wales','Sydney');
14 insert into hier values('California','Redwood Shores');
15 insert into hier values('Canada','Ontario');
16 insert into hier values('China','Beijing');
17 insert into hier values('England','London');
18 insert into hier values('Europe','United Kingdom');
19 insert into hier values('Japan','Osaka');
20 insert into hier values('Japan','Tokyo');
21 insert into hier values('North America','Canada');
22 insert into hier values('North America','USA');
23 insert into hier values('Ontario','Ottawa');
24 insert into hier values('Ontario','Toronto');
25 insert into hier values('USA','California');
26 insert into hier values('United Kingdom','England');



那么我們可以使用START WITH . . . CONNECT BY . . .從句將父級地區與孩子地區連接起來,並將其層次等級顯示出來。

View Code
 1 column child format a40
2 select level,lpad(' ',level*3)||child child
3 from hier
4 start with parent is null
5 connect by prior child = parent;
6
7 LEVEL CHILD
8 ---------- --------------------------
9 1 Asia
10 2 China
11 3 Beijing
12 2 Japan
13 3 Osaka
14 3 Tokyo
15 1 Australia
16 2 New South Wales
17 3 Sydney
18 1 Europe
19 2 United Kingdom
20 3 England
21 4 London
22 1 North America
23 2 Canada
24 3 Ontario
25 4 Ottawa
26 4 Toronto
27 2 USA
28 3 California
29 4 Redwood Shores



自從Since Oracle 9i 開始,就可以通過SYS_CONNECT_BY_PATH 函數實現將從父節點到當前行內容以“path”或者層次元素列表的形式顯示出來。 如下例所示:

column path format a50
select level,sys_connect_by_path(child,'/') path
from hier
start with parent is null
connect by prior child = parent;

LEVEL PATH
-------- --------------------------------------------
1 /Asia
2 /Asia/China
3 /Asia/China/Beijing
2 /Asia/Japan
3 /Asia/Japan/Osaka
3 /Asia/Japan/Tokyo
1 /Australia
2 /Australia/New South Wales
3 /Australia/New South Wales/Sydney
1 /Europe
2 /Europe/United Kingdom
3 /Europe/United Kingdom/England
4 /Europe/United Kingdom/England/London
1 /North America
2 /North America/Canada
3 /North America/Canada/Ontario
4 /North America/Canada/Ontario/Ottawa
4 /North America/Canada/Ontario/Toronto
2 /North America/USA
3 /North America/USA/California
4 /North America/USA/California/Redwood Shores



 
在 Oracle 10g 中,還有其他更多關於層次查詢的新特性 。例如,有的時候用戶更關心的是每個層次分支中等級最低的內容。那么你就可以利用偽列函數CONNECT_BY_ISLEAF來判斷當前行是不是葉子。如果是葉子就會在偽列中顯示“1”,如果不是葉子而是一個分支(例如當前內容是其他行的父親)就顯示“0”。下給出了一個關於這個函數使用的例子:

View Code
select connect_by_isleaf,sys_connect_by_path(child,'/') path
from hier
start with parent is null
connect by prior child = parent;

CONNECT_BY_ISLEAF PATH
----------------------------------
0 /Asia
0 /Asia/China
1 /Asia/China/Beijing
0 /Asia/Japan
1 /Asia/Japan/Osaka
1 /Asia/Japan/Tokyo
0 /Australia
0 /Australia/New South Wales
1 /Australia/New South Wales/Sydney
0 /Europe
0 /Europe/United Kingdom
0 /Europe/United Kingdom/England
1 /Europe/United Kingdom/England/London
0 /North America
0 /North America/Canada
0 /North America/Canada/Ontario
1 /North America/Canada/Ontario/Ottawa
1 /North America/Canada/Ontario/Toronto
0 /North America/USA
0 /North America/USA/California
1 /North America/USA/California/Redwood Shores



 
在Oracle 10g 中還有一個新操作――CONNECT_BY_ROOT。 它用在列名之前用於返回當前層的根節點。如下面的例子,我可以顯示出層次結構表中當前行數據所對應的最高等級節點的內容。

View Code
select connect_by_root child,sys_connect_by_path(child,'/') path
from hier
start with parent is null
connect by prior child = parent;

CONNECT_BY_ROOT PATH
------------------------------ --------
Asia /Asia
Asia /Asia/China
Asia /Asia/China/Beijing
Asia /Asia/Japan
Asia /Asia/Japan/Osaka
Asia /Asia/Japan/Tokyo
Australia /Australia
Australia /Australia/New South Wales
Australia /Australia/New South Wales/Sydney
Europe /Europe
Europe /Europe/United Kingdom
Europe /Europe/United Kingdom/England
Europe /Europe/United Kingdom/England/London
North America /North America
North America /North America/Canada
North America /North America/Canada/Ontario
North America /North America/Canada/Ontario/Ottawa
North America /North America/Canada/Ontario/Toronto
North America /North America/USA
North America /North America/USA/California
North America /North America/USA/California/Redwood Shores



在Oracle 10g 之前的版本中,如果在你的樹中出現了環狀循環(如一個孩子節點引用一個父親節點),Oracle 就會報出一個錯誤提示:“ ORA-01436: CONNECT BY loop in user data”。如果不刪掉對父親的引用就無法執行查詢操作。而在 Oracle 10g 中,只要指定“NOCYCLE”就可以進行任意的查詢操作。與這個關鍵字相關的還有一個偽列――CONNECT_BY_ISCYCLE, 如果在當前行中引用了某個父親節點的內容並在樹中出現了循環,那么該行的偽列中就會顯示“1”,否則就顯示“0”。如下例所示:

create table hier2
(parent number,
 child  number
);

insert into hier2 values(null,1);
insert into hier2 values(1,2);
insert into hier2 values(2,3);
insert into hier2 values(3,1);

select connect_by_iscycle,sys_connect_by_path(child,'/') path
from hier2
start with parent is null
connect by nocycle prior child = parent;

CONNECT_BY_ISCYCLE PATH
------------------ -------
0 /1
0 /1/2
1 /1/2/3
--------------------------------------------------------------------------------------------------------

-END-


Reference to:
========================================================================================================
1.Oracle10g中新型層次查詢選項簡介(http://www.erp100.com/html/43/2743-6759345.html)
2.Oracle 中使用層次查詢方便處理財務報表(http://blog.csdn.net/wqsmiling/archive/2005/06/14/394404.aspx)
3.樹結構和它的專用函數SYS_CONNECT_BY_PATH(http://blog.oracle.com.cn/html/83/t-122083.html)
4.START WITH and CONNECT BY in Oracle SQL(http://www.adp-gmbh.ch/ora/sql/connect_by.html)



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM