Oracle層級查詢語句(hierarchical query)connect by 用法詳解
摘要:
1、connect by中的條件就表示了父子之間的連線關係,比如 connect by id=prior pid。
2、prior,表示在一表上,prior所在列pid的某一值A的記錄行的父親是列id上值等於列pid上值A的記錄行。
3、LEVEL偽列表示樹的深度(或叫高度)。
oracle中的select語句可以用START WITH...CONNECT BYPRIOR子句實現遞迴查詢(或叫樹狀查詢),connect by 是結構化查詢中用到的,
其基本語法是:
select ... from <TableName>
where <Conditional-1>
start with <Conditional-2>
connect by <Conditional-3>
;
<Conditional-1>:過濾條件,用於對返回的所有記錄進行過濾。
<Conditional-2>:該限定條件,表示查詢結果以誰作為起始根結點的。當然可以放寬該限定條件,以取得多個根結點,實際就是多棵樹。
<Conditional-3>:連線條件,即表示不同節點間以該連線條件構成一個父子關係
或說是
1 2 |
[ START WITH condition ] CONNECT BY [ NOCYCLE ] condition |
或說
其中 connect by 與 start with 語句擺放的先後順序不影響查詢的結果,[where 條件1]可以不需要,若是出現則要放在 connect by 與 start with 語句之前,否則出錯。
[where 條件1]、[條件2]、[條件3]各自作用的範圍都不相同:
[where 條件1]
是在根據“connect by [條件2] start with [條件3]”選擇出來的記錄中進行過濾,是針對單條記錄的過濾, 不會考慮樹的結構(最後的過濾);
[ connect by 條件2]
指定構造樹的條件,以及對樹分支的過濾條件,在這裡執行的過濾會把符合條件的記錄及其下的所有子節點都過濾掉;
[ start with 條件3]
限定作為搜尋起始點的條件,如果是自上而下的搜尋則是限定作為根節點的條件,如果是自下而上的搜尋則是限定作為葉子節點的條件;
要根據connect by 從上到下還是從下到上,來確定起始節點,可能是葉節點,也可能是父節點,這些開始節點可以是多個,並且包含這些節點。
oracle 11g r2貌似不支援從下到上的遍歷??oracle 10g支援從下到上的遍歷??
註釋:
0、
層級查詢語句(hierarchical query)中,where子句先被執行,再執行CONNECT BY子句以及其附屬子句。
LEVEL 偽列表示樹的深度(或叫高度)。
使用LEVEL偽列:
在具有樹結構的表中,每一行資料都是樹結構中的一個節點,由於節點所處的層次位置不同,所以每行記錄都可以有一個層號。層號根據節點與根節點的距離確定。不論從哪個節點開始,該起始根節點的層號始終為1,根節點的子節點為2, 依此類推。
1、這裡說的節點指的是層級查詢語句(hierarchical query)中from子句裡的表的每一資料行。
2、層級查詢語句(hierarchical query)中,CONNECT BY子句是必選的,而START WITH子句是可選的,START WITH子句是用來修飾CONNECT BY子句的。
3、prior關鍵字放在CONNECT BY子句中。其中用PRIOR表示上一條記錄,比如 CONNECT BY PRIOR id = parent_id就是說上一條記錄的id 是本條記錄的parent_id,即本記錄的父親是上一條記錄。
例如,
connect by parent_id =prior id
表示的是一資料行中列名為parent_id的列上的值為該行的父節點的編號值,而
父節點的編號值都是來源於表中列名為id的列上的各個值。
總之,prior放在connect by子句連線條件裡的哪一邊,哪一邊就是父節點的編號值的來源,而connect
by子句連線條件裡等號另一邊就是記錄一資料行其對應的父節點的編號值。
4、
START WITH 子句為可選項,用來標識哪個節點作為查詢樹型結構的根節點。若該子句被省略,則表示所有滿足查詢條件(即where子句裡的條件)的行作為根節點。具體例子見下文二大點裡的註釋。
START WITH: 不但可以指定一個根節點,還可以指定多個根節點。
“START WITH 子句被省略,則表示所有滿足查詢條件(即where子句裡的條件)的行作為根節點”這個特性
可以用來證明
”層級查詢語句(hierarchical query)中,where子句先被執行,再執行CONNECT BY子句以及其附屬子句“。
5、START WITH 子句和CONNECT BY子句是兩個相互獨立的子句,即並沒有規定START
WITH 子句出現的列就是要為CONNECT BY子句裡那個帶有關鍵字prior的列,START WITH 子句出現的列可以來自表裡的任何列,也就是說START
WITH 子句出現的列可以沒有在START WITH 子句裡出現,因為START
WITH 子句的作用就是根據START WITH 子句的限定條件來篩選出哪些資料行作為根節點而已。例子,
select * from t2 start with id = 1 connect by prior id= root_id;
select * from t2 start with root_id = 0 connect by prior id = root_id;
層級查詢語句(hierarchical query),
Oracle採用了自上而下的深度優先的演算法。
下面以一個具體例子來說明層級查詢語句(hierarchical query)connect by 用法。
現在有一個表t2,
其上各個資料行間的父子關係的結構圖(即資料組織結構圖)如下:
表t2的表結構如下:
create table t2(
root_id number,
id number,
name varchar(5),
description varchar(10)
);
insert into t2(root_id,id,name,description)values(0,1,'a','aaa');
insert into t2(root_id,id,name,description)values(1,2,'a1','aaa1');
insert into t2(root_id,id,name,description)values(1,3,'a2','aaa2');
insert into t2(root_id,id,name,description)values(0,4,'b','bbb');
insert into t2(root_id,id,name,description)values(4,5,'b1','bbb1');
insert into t2(root_id,id,name,description)values(4,6,'b2','bbb2');
一、
獲取完整樹:
select * from t2 start withroot_id = 0connect by prior id = root_id;
註釋:
簡單說來是將一個樹狀結構儲存在一張表裡,比如一個表中存在兩個欄位: root_id,id。那麼通過表示每一條記錄的父記錄是誰,就可以形成一個樹狀結構。
用上述語法的查詢可以取得這棵樹的所有記錄。
上述SQL語句具體執行過程分析:
1) 首先,執行的是該select語句中的start with子句部分,具體地說,就是執行該select語句的伺服器程序先從表t2中找出所有 root_id欄位的值為 0的資料行。在上述例子中,就是name欄位分別為a和b的兩條資料行,它們兩構成了上圖中樹的第一層。
2)接著,執行的是該select語句中的connect by子句部分,具體地說,就是執行該select語句的伺服器程序先從表t2剩下的資料行(即除去了已經被選中的那些資料行)中找出所有root_
id欄位的值等於在上圖的樹中位於其上一層中各個資料行的id欄位的值的資料行。在上述例子中,上圖中樹的第一層裡name欄位為a的資料行的id欄位值為1,所以伺服器程序就從表t2剩下的資料行(即出去了上圖中樹的第一層裡的那兩條資料行)中找出所有root_
id欄位的值等於1的資料行,即name欄位分別為a1和a2的兩條資料行;上圖中樹的第一層裡name欄位為b的資料行的id欄位值為4,所以伺服器程序就從表t2剩下的資料行(即出去了上圖中樹的第一層裡的那兩條資料行以及前面剛被選中的兩條資料行)中找出所有root_
id欄位的值等於4的資料行,即name欄位分別為b1和b2的兩條資料行。於是,name欄位分別為a1和a2,b1和b2的四條資料行,構成了上圖中樹的第二層。
3)再接著,伺服器程序還是要重複執行該select語句中的connect by子句部分,直到表中不存在符合prior id = root_id這個條件的資料行為止。
此時,伺服器程序發現表t2中剩餘的資料行為空了。
這樣,該select語句整個過程就執行完畢了。如果表t2中還是有剩餘的資料行的,則伺服器程序重複執行2)步驟。
註釋:
1)In a hierarchicalquery, one expression incondition must be qualifiedwith thePRIOR operator to refer to the parentrow. For example,
... PRIOR expr = expr
or
... expr = PRIOR expr
譯文:
在 層次查詢(或叫遞迴查詢)中,connect by子句的條件裡的表示式必須要有prior運算子 放在一個欄位的前面來表示該欄位是父資料行((即樹結構圖中位於子資料行所在層的直接上一層裡的資料行))的該欄位。例如,
... PRIOR expr = expr
or
... expr = PRIOR expr
也就是說,當connect by子句的條件裡出現的欄位是普通欄位,不是偽列欄位rownum或是level時,connect by子句的條件裡的表示式必須要有prior運算子 放在一個欄位的前面;當connect by子句的條件裡出現的是偽列欄位rownum或是level時,connect by子句的條件裡的表示式不用出現prior運算子。
還有一點要說明的是,start with子句不是一個獨立的子句,即start with子句是一個不能單獨出現在SQL語句的子句,必須在SQL語句中出現了connect by子句後,才能出現。換句話說,start with子句是來配合connect by子句的。例如,
SQL> select * from t2 start with id = 1;
select * from t2 start withid = 1
*
第 1 行出現錯誤:
ORA-01788: 此查詢塊中要求 CONNECT BY 子句
附加:其他依賴於CONNECT BY 子句的偽列,如level、CONNECT_BY_ISCYCLE等也會有相同的提示,若是獨立出現的話。
當connect by子句沒有帶start with子句時,例如,select
* from t2 connect by prior id = root_id,則因為帶prior的id欄位是父資料行的欄位,所以執行該select語句的伺服器程序先從表t2中找出所有 id欄位值非空的資料行(差不多就是表t2的所有資料行),它們構成了樹結構的第一層。接著,執行該select語句的伺服器程序從表t2中找出所有root_ id欄位的值等於在樹結構的第一層中各個資料行的id欄位值的資料行,它們構成了樹結構的第二層。再接著,伺服器程序從表t2中找出所有root_
id欄位的值等於在樹結構的第二層中各個資料行的id欄位值的資料行。就這樣,一直重複執行connect by prior id = root_id這一個子句,直到表中不存在符合prior id = root_id這個條件的資料行為止。
下面是該SQL語句的執行結果:
SQL> select t2.*,level from t2 connect by prior id = root_id;
LEVEL
ID ROOT_ID NAME DESCRIPTIO
---------- ---------- ----- ---------- ----------
1 1 0 a aaa
1 2 1 a1 aaa1
1 3 1 a2 aaa2
1 4 0 b bbb
1 5 4 b1 bbb1
1 6 4 b2 bbb2
2 2
1 a1 aaa1
2 3
1 a2 aaa2
2 5 4 b1 bbb1
2
6 4 b2 bbb2
已選擇10行。
註釋:
ID表示樹狀結構圖中各個節點的編號值。
LEVEL偽列表示樹的深度(或叫高度)。
當connect by子句帶有start with子句時,例如,select * from t2 start with root_id = 0 connect by prior id = root_id,則執行該select語句的伺服器程序首先執行的是該select語句中的start with子句部分。start with子句部分的功能就是讓伺服器程序根據start with子句裡的欄位找到樹結構的第一層裡所有的資料行。找完第一層的資料行後,伺服器程序執行的操作和connect by子句沒帶有start with子句時的操作就是一樣的了,也是重複執行connect by prior id = root_id這一個子句,直到表中不存在符合prior id = root_id這個條件的資料行為止。
SQL>select * from t2 start with root_id = 0 connect by prior id = root_id;
LEVEL
ID ROOT_ID NAME DESCRIPTIO
---------- ---------- ----- ---------- ----------
1 1 0 a aaa
1 4 0 b bbb
1 5 4 b1 bbb1
1 6 4 b2 bbb2
2 2
1 a1 aaa1
2 3
1 a2 aaa2
已選擇6行。
雖然這個SQL語句中start with子句裡的欄位和connect by子句裡的帶prior的欄位不一樣,但是不會相互影響的。
2)在connect by 子句裡出現的兩個欄位都是同一個表裡的兩個欄位。
3) “執行該select語句的伺服器程序先從表t2剩下的資料行(即除去了已經被選中的那些資料行)中查詢資料行“這樣的表述是不正確的。其實,每次伺服器程序都是從表t2中的所有的資料行上查詢的。例如,
SQL> select t2.*,level from t2 connect by prior id = root_id;
ROOT_ID ID NAME DESCRIPTIO LEVEL
---------- ---------- ----- --------------------
0 1 a aaa 1
1 2 a1 aaa1 1
1 3 a2 aaa2 1
0 4 b bbb 1
4 5 b1 bbb1 1
4 6 b2 bbb2 1
1 2 a1 aaa1 2
1 3 a2 aaa2 2
4 5 b1 bbb1 2
4 6 b2 bbb2 2
已選擇10行。
從上面的例子可以看出,處於樹結構第二層(level=2)的資料行也在處於樹結構第一層(level=1)中出現。這就說明了,每次伺服器程序都是從表t2中的所有的資料行上查詢的。
4)當表中有重複行時,這些重複行不會合並在一起,而是單獨算的。例如,
SQL> insert into t2(root_id,id,name,description)values(4,6,'b2','bbb2');
已建立 1 行。
SQL> select t22.*,level from t2 connect by prior id = root_id order bylevel,i
d;
ROOT_ID ID NAME DESCRIPTIO LEVEL
---------- ---------- ----- ---------- ----------
0 1 a aaa 1
1 2 a1 aaa1 1
1 3 a2 aaa2 1
0 4 b bbb 1
4 5 b1 bbb1 1
4 6 b2 bbb2 1
4 6 b2 bbb2 1
1 2 a1 aaa1 2
1 3 a2 aaa2 2
4 5 b1 bbb1 2
4 6 b2 bbb2 2
4 6 b2 bbb2 2
已選擇12行。
二、
獲取特定子樹:
select * from t2 start with id = 1 connect by prior id= root_id;
select * from t2 start with id = 4 connect by prior id = root_id;
如果connect by prior中的prior被省略,則查詢將不進行深層遞迴。如:
select * from t2 start with root_id = 0 connect by id = root_id;//此時 start with子句還是有被執行的
select * from t2 start with id = 1 connect by id = root_id;如:
註釋:
上述兩個例子如果connect by prior中的關鍵字prior被省略,且在connect by 裡的欄位不是偽列rownum或是偽列level(偽列level只有在SQL語句中出現了connect by 子句時才有的),那麼connect by子句會被忽略不被執行的(此時 start with子句還是有被執行的)。
參考:
http://www.360doc.com/content/11/0608/11/5287961_122421161.shtml
Hierarchical Queries
http://www.cnblogs.com/zhf/archive/2008/09/10/1288101.html
另見:
==================================================================
附加
Oracle “CONNECT BY”是層次查詢子句,一般用於樹狀或者層次結果集的查詢。其語法是:
1 2 |
[ START WITHcondition ] CONNECT BY [ NOCYCLE ] condition |
The start with .. connect by clause can be used toselect data that has a hierarchical relationship (usually some sort ofparent->child (boss->employee or thing->parts).
說明:
1. START WITH:告訴系統以哪個節點作為根結點開始查詢並構造結果集,該節點即為返回記錄中的最高節點。
2. 當分層查詢中存在上下層互為父子節點的情況時,會返回ORA-01436錯誤。此時,需要在connect by後面加上NOCYCLE關鍵字。同時,可用connect_by_iscycle偽列定位出存在互為父子迴圈的具體節點。 connect_by_iscycle必須要跟關鍵字NOCYCLE結合起來使用,也就說,connect_by_iscycle偽列是一個不能單獨出現在SQL語句的偽列,必須在SQL語句中出現了關鍵字NOCYCLE後,才能出現。例如,
select connect_by_iscycle from t2 start with root_id = 0 connect byNOCYCLE prior id = root_id;
接下來,用一些示例來說明“CONNECT BY”的用法。
[例1]
建立一個部門表,這個表有三個欄位,分別對應部門ID,部門名稱,以及上級部門ID
1 2 3 4 5 6 7 8 |
-- Create table create table DEP ( DEPID number(10)notnull, DEPNAME varchar2(256), UPPERDEPID number(10) ) ; |
初始化一些資料
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(0, '總經辦',null); 1 row inserted SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(1, '開發部', 0); 1 row inserted SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(2, '測試部', 0); 1 row inserted SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(3, 'Sever開發部', 1); 1 row inserted SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(4, 'Client開發部', 1); 1 row inserted SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(5, 'TA測試部', 2); 1 row inserted SQL> INSERTINTODEP(DEPID, DEPNAME, UPPERDEPID)VALUES(6, '專案測試部', 2); 1 row inserted SQL> commit; Commit complete |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
SQL> SELECT*FROM DEP; DEPID DEPNAME UPPERDEPID ----------- -------------------------------------------------------------------------------- ----------- 0 General Deparment 1 Development 0 2 QA 0 3 Server Development 1 4 Client Development 1 5 TA 2 6 Porject QA 2 7 rowsselected |
現在我要根據“CONNECT BY”來實現樹狀查詢結果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
SQL> SELECTRPAD(' ', 2*(LEVEL-1),'-') || DEPNAME "DEPNAME", CONNECT_BY_ROOT DEPNAME"ROOT", CONNECT_BY_ISLEAF "ISLEAF", LEVEL , SYS_CONNECT_BY_PATH(DEPNAME,'/')"PATH" FROM DEP START WITHUPPERDEPIDIS NULL CONNECT BY PRIOR DEPID = UPPERDEPID; DEPNAME ROOT ISLEAF LEVELPATH ------------------------------ ------------------- ---------- ---------- -------------------------------------------------------------------------------- General Deparment General Deparment 0 1 /General Deparment -Development General Deparment 0 2 /General Deparment/Development ---Server Development General Deparment 1 3 /General Deparment/Development/Server Development ---Client Development General Deparment 1 3 /General Deparment/Development/Client Development -QA General Deparment 0 2 /General Deparment/QA ---TA General Deparment 1 3 /General Deparment/QA/TA ---Porject QA General Deparment 1 3 /General Deparment/QA/Porject QA 7 rowsselected |
說明:
1. CONNECT_BY_ROOT (偽列)返回當前節點的最頂端節點
2. CONNECT_BY_ISLEAF (偽列)判斷是否為葉子節點,如果這個節點下面有子節點,則不為葉子節點
3. LEVEL 偽列表示樹的深度(或叫高度)
4. SYS_CONNECT_BY_PATH函式顯示詳細路徑,並用“/”分隔
CONNECT BY的應用例子
[例2]
通過CONNECT BY用於十六進度轉換為十進位制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
CREATE OR REPLACE FUNCTION f_hex_to_dec(p_str IN VARCHAR2) RETURN VARCHAR2 IS ---------------------------------------------------------------------------------------------------------------------- -- 物件名稱: f_hex_to_dec -- 物件描述: 十六進位制轉換十進位制 -- 輸入引數: p_str 十六進位制字串 -- 返回結果: 十進位制字串 -- 測試用例: SELECT f_hex_to_dec('78A') FROM dual; ---------------------------------------------------------------------------------------------------------------------- v_return VARCHAR2(4000); BEGIN SELECT SUM(DATA) INTO v_return FROM (SELECT (CASE upper(substr(p_str, rownum, 1)) WHEN 'A' THEN '10' WHEN 'B' THEN '11' WHEN 'C' THEN '12' WHEN 'D' THEN '13' WHEN 'E' THEN '14' WHEN 'F' THEN '15' ELSE substr(p_str, rownum, 1) END) * power(16, length(p_str) - rownum) DATA FROM dual CONNECT BY rownum <= length(p_str)); RETURN v_return; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; |
說明:
1. CONNECT BY rownum <= length(p_str))對輸入的字串進行逐個遍歷
2. 通過CASE語句,來解析十六進位制中的A-F對應的10進位制值
[例3]
通過CONNECT BY生成序列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
SQL> SELECT ROWNUM FROM DUAL CONNECT BY ROWNUM <= 10; ROWNUM ---------- 1 2 3 4 5 6 7 8 9 10 10 rows selected |
本例子的具體詳解見:《深入理解connect byrownum<xxx》
參考:
http://www.cnblogs.com/lettoo/archive/2010/08/03/1791239.html
http://www.blogjava.net/freeman1984/archive/2011/05/06/349668.html
百度 connect by prior