START WITH...CONNECT BY
文章出處:
http://blog.sina.com.cn/s/blog_6973afc00100wf5p.html
[轉載]Oracle層次查詢及應用(start with connect by)(2011-08-12 10:43:27)
摘要:本文將根據對層次查詢語句簡單例子的說明來理解應用,並舉例實際的應用案例。
========================================================================================================
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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- [轉載] Oracle:start with...connect by子句的用法Oracle
- start uniappAPP
- rac中 crsctl start/stop crs and crsctl start/stop cluster 區別
- 3.1.5.6 Forcing an Instance to Start
- 開發springboot startSpring Boot
- AUTO START ORACLE ON LINUX(zt)OracleLinux
- where to start, from where the end
- [Kick Start] 2021 Round B
- Teamcenter 《POM has not start》處理
- Oracle start with connect by PostgreSQL recursive cteOracleSQL
- Oracle Database Cloud - Database as a Service Quick StartOracleDatabaseCloudUI
- React-App:NPM start 報錯ReactAPPNPM
- Linux下安裝Apollo (Quick Start)LinuxUI
- Failed to start docker.service: Unit not foundAIDocker
- mysql服務啟動報錯Redirecting to systemctl start mysqld.serviceFailed to start mysqld.service:Unit not found.MySqlAI
- Spring Boot 動手寫一個 StartSpring Boot
- [fabric]Cannot start service orderer: Mounts denied: In MacMac
- PostgreSQL pg_ctl start超時分析SQL
- linux start_udev 導致VIP漂移Linuxdev
- uboot中start.s原始碼指令boot原始碼
- android studio Error:Unable to start the daemon process【轉】AndroidError
- Llama-recipes Quick start 的除錯UI除錯
- docker-compose up start restart區別DockerREST
- SpringBoot自動裝配-自定義StartSpring Boot
- 按下「START」,開始雲遊戲遊戲
- cannot read prpperty ‘start‘ of undefined(問題記錄)Undefined
- Your ApplicationContext is unlikely to start due to a @ComponentScan of the default package.APPContextPackage
- Server Tomcat v9.0 Server at localhost failed to start.ServerTomcatlocalhostAI
- hyperf 服務管理指令碼 start、restart、stop、status指令碼REST
- Spark文件閱讀之二:Programming Guides - Quick StartSparkGUIIDE
- 騰訊的START雲遊戲體驗如何?遊戲
- gf 框架快速開始骨架 gf-start-kit框架
- RAT ORA-15507: cannot start workload replay on instance 1
- 使用 .NET MAUI 建立移動應用——Get StartUI
- arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o start.o start.s 什麼意思? 2020-11-21LinuxGC
- 為啥呼叫new Thread().start()方法會呼叫run()方法?thread
- 我為什麼看好騰訊START雲遊戲遊戲
- 優秀的應用快速啟動工具:start for MacMac
- This application failed to start because it could not find or load the Qt platform plugin “windows“APPAIQTPlatformPluginWindows