START WITH...CONNECT BY

OraFige發表於2012-01-04

文章出處:

http://blog.sina.com.cn/s/blog_6973afc00100wf5p.html

[轉載]Oracle層次查詢及應用(start with connect by)(2011-08-12 10:43:27)

標籤:

轉載

分類: oracle
oracle層級查詢

摘要:本文將根據對層次查詢語句簡單例子的說明來理解應用,並舉例實際的應用案例。
========================================================================================================
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子句遞迴查詢一般用於一個表維護樹形結構的應用。可以通過一個簡單的例子來理解其使用的概念和方法。
建立示例表:
--------------------------------------------------------------------------------------------------------
CREATE TABLE tbl_test
(
  ID    NUMBER,
  NAME  VARCHAR2(10),
  pid   NUMBER DEFAULT 0
);
 
插入測試資料:
--------------------------------------------------------------------------------------------------------
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 

從父記錄行向子記錄行遞迴
--------------------------------------------------------------------------------------------------------
SELECT     *
      FROM tbl_test
START WITH ID = 1
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,以上結果因此而來。
 
從子記錄向父記錄遞迴
--------------------------------------------------------------------------------------------------------
SELECT     *
      FROM tbl_test
START WITH ID = 5
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
--------------------------------------------------------------------------------------------------------
SELECT     LEVEL, pid,
           ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid) by_pid,
           ROW_NUMBER () OVER (ORDER BY pid) AS rn, NAME
      FROM tbl_test
START WITH pid = 0
CONNECT BY PRIOR ID = pid
  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 

根據父值逐層區分
--------------------------------------------------------------------------------------------------------
SELECT     pid, ID,
           (CASE
               WHEN LEVEL = 1
                  THEN NAME
               WHEN LEVEL = 2
                  THEN '             ' || NAME
               WHEN LEVEL = 3
                  THEN '                           ' || NAME
            END
           ) bs_name
      FROM tbl_test
START WITH pid = 0
CONNECT BY pid = PRIOR ID

記錄如下:
--------------------------------------------------------------------------------------------------------
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

指令碼:
--------------------------------------------------------------------------------------------------------
SELECT     pid, SYS_CONNECT_BY_PATH (by_path, ',')
      FROM (SELECT   pid,
                     ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid)
                                                                       by_pid,
                     ROW_NUMBER () OVER (ORDER BY pid) + pid AS rn,
                     NAME AS by_path
                FROM tbl_test
            ORDER BY 1)
START WITH by_pid = 1
CONNECT BY rn - 1 = PRIOR rn
  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即可,如下指令碼:
--------------------------------------------------------------------------------------------------------
SELECT     pid, LTRIM (MAX (SYS_CONNECT_BY_PATH (by_path, ',')), ',')
      FROM (SELECT   pid,
                     ROW_NUMBER () OVER (PARTITION BY pid ORDER BY pid)
                                                                       by_pid,
                     ROW_NUMBER () OVER (ORDER BY pid) + pid AS rn,
                     NAME AS by_path
                FROM tbl_test
            ORDER BY 1)
START WITH by_pid = 1
CONNECT BY rn - 1 = PRIOR rn
  GROUP BY pid
  ORDER BY 1

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


應用:
一、ERP BOM(物料清單)
========================================================================================================
SELECT DISTINCT b.lvl lv, msi1.segment1 p_item, msi1.description p_item_desc,
                msi1.primary_uom_code, b.item_num num, b.operation_seq_num,
                msi2.segment1 c_item, msi2.description c_item_desc,
                msi2.primary_uom_code, b.component_quantity,
                b.component_yield_factor,
                DECODE (b.wip_supply_type,
                        1, 'Push',
                        2, 'Assembly Pull'
                       ) TYPE, b.supply_subinventory, b.planning_factor
           FROM inv.mtl_system_items_b msi1,
                inv.mtl_system_items_b msi2,
                bom.bom_structures_b bom,
                inv.mtl_parameters mp,
                (SELECT     LEVEL lvl, bic.bill_sequence_id,
                            bic.component_item_id, bic.component_quantity,
                            bic.component_yield_factor, bic.operation_seq_num,
                            bic.item_num, bic.wip_supply_type,
                            bic.supply_subinventory, bic.effectivity_date,
                            bic.planning_factor
                       FROM bom.bom_components_b bic
                      WHERE disable_date IS NULL AND bic.planning_factor > 0
                 START WITH bic.bill_sequence_id IN (
                               SELECT bill_sequence_id
                                 FROM bom.bom_structures_b bom2,
                                      inv.mtl_system_items_b msi,
                                      inv.mtl_parameters mp
                                WHERE bom2.assembly_item_id = msi.inventory_item_id
                                  AND bom2.organization_id = msi.organization_id
                                  AND msi.segment1 = 'FQH1AU3ACBBH34HD02'
                                  AND mp.organization_code = 'ZP1'
                                  AND msi.organization_id = mp.organization_id
                                  AND bom2.alternate_bom_designator IS NULL)
                 CONNECT BY bic.bill_sequence_id =
                               PRIOR (SELECT DISTINCT bill_sequence_id
                                                 FROM bom.bom_structures_b bo,
                                                      inv.mtl_system_items_b msi,
                                                      inv.mtl_parameters mp
                                                WHERE bo.assembly_item_id = bic.component_item_id
                                                  AND mp.organization_code ='ZP1'
                                                  AND bo.organization_id = mp.organization_id
                                                  AND bo.organization_id = msi.organization_id
                                                  AND bo.assembly_item_id =msi.inventory_item_id
                                                  AND bo.alternate_bom_designator IS NULL
                                                  AND disable_date IS NULL)) b
          WHERE b.bill_sequence_id = bom.bill_sequence_id
            AND mp.organization_code = 'ZP1'
            AND bom.organization_id = mp.organization_id
            AND bom.organization_id = msi1.organization_id
            AND bom.assembly_item_id = msi1.inventory_item_id
            AND bom.organization_id = msi2.organization_id
            AND b.component_item_id = msi2.inventory_item_id

二、行列轉換
========================================================================================================
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中可以:
--------------------------------------------------------------------------------------------------------
SELECT     deptno,
           LTRIM
              (MAX (SYS_CONNECT_BY_PATH (ename, ','))KEEP (DENSE_RANK LAST ORDER BY curr),
               ','
              ) AS concatenated
      FROM (SELECT deptno, ename,
                   ROW_NUMBER () OVER (PARTITION BY deptno ORDER BY ename)
                                                                      AS curr,
                     ROW_NUMBER () OVER (PARTITION BY deptno ORDER BY ename)
                   - 1 AS prev
              FROM emp)
  GROUP BY deptno
CONNECT BY prev = PRIOR curr AND deptno = PRIOR deptno
START WITH curr = 1;

記錄如下:
--------------------------------------------------------------------------------------------------------
DEPTNO CONCATENATED
10 CLARK,KING,MILLER 
20 ADAMS,FORD,JONES,SCOTT,SMITH 
30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD 

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

create table hier
 (parent varchar2(30),
  child varchar2(30)
);

insert into hier values(null,'Asia');
insert into hier values(null,'Australia');
insert into hier values(null,'Europe');
insert into hier values(null,'North America');
insert into hier values('Asia','China');
insert into hier values('Asia','Japan');
insert into hier values('Australia','New South Wales');
insert into hier values('New South Wales','Sydney');
insert into hier values('California','Redwood Shores');
insert into hier values('Canada','Ontario');
insert into hier values('China','Beijing');
insert into hier values('England','London');
insert into hier values('Europe','United Kingdom');
insert into hier values('Japan','Osaka');
insert into hier values('Japan','Tokyo');
insert into hier values('North America','Canada');
insert into hier values('North America','USA');
insert into hier values('Ontario','Ottawa');
insert into hier values('Ontario','Toronto');
insert into hier values('USA','California');
insert into hier values('United Kingdom','England');

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

column child format a40
select level,lpad(' ',level*3)||child child
from hier
start with parent is null
connect by prior child = parent;

LEVEL CHILD
---------- --------------------------
1 Asia
2 China
3 Beijing
2 Japan
3 Osaka
3 Tokyo
1 Australia
2 New South Wales
3 Sydney
1 Europe
2 United Kingdom
3 England
4 London
1 North America
2 Canada
3 Ontario
4 Ottawa
4 Toronto
2 USA
3 California
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”。下給出了一個關於這個函式使用的例子:

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。它用在列名之前用於返回當前層的根節點。如下面的例子,我可以顯示出層次結構表中當前行資料所對應的最高等級節點的內容。

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)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26464953/viewspace-714388/,如需轉載,請註明出處,否則將追究法律責任。

相關文章