oracle中connect by prior實現遞歸查詢
收集的幾條在oracle中通過connect by prior來實現遞歸查詢 Start with...Connect By子句遞歸查詢一般用於一個表維護樹形結構的應用。 創建示例表: CREATE TABLE TBL_TEST ( ID NUMBER, NAME VARCHAR2(100 BYTE), PID NUMBER DEFAULT 0 ); 插入測試數據: INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('1','10','0'); INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('2','11','1'); INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('3','20','0'); INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('4','12','1'); INSERT INTO TBL_TEST(ID,NAME,PID) VALUES('5','121','2'); 從Root往樹末梢遞歸 select * from TBL_TEST start with id=1 connect by prior id = pid 從末梢往樹ROOT遞歸 select * from TBL_TEST start with id=5 connect by prior pid = id =============================================================================================================== 有一張表 t 字段: parent child 兩個字段的關係是父子關係
寫一個sql語句,查詢出指定父下面的所有的子
比如
a b a c a e b b1 b b2 c c1 e e1 e e3 d d1
指定parent=a,選出 a b a c a e b b1 b b2 c c1 e e1 e e3
SQL語句: select parent,child from test start with parent='a' connect by prior child=parent ================================================================================================ connect by 是結構化查詢中用到的,其基本語法是: select ... from tablename start by cond1 connect by cond2 where cond3; 簡單說來是將一個樹狀結構存儲在一張表裏,比如一個表中存在兩個字段: id,parentid那麼通過表示每一條記錄的parent是誰,就可以形成一個樹狀結構。 用上述語法的查詢可以取得這棵樹的所有記錄。 其中COND1是根結點的限定語句,當然可以放寬限定條件,以取得多個根結點,實際就是多棵樹。 COND2是連接條件,其中用PRIOR表示上一條記錄,比如 CONNECT BY PRIOR ID=PRAENTID就是說上一條記錄的ID是本條記錄的PRAENTID,即本記錄的父親是上一條記錄。 COND3是過濾條件,用於對返回的所有記錄進行過濾。 PRIOR和START WITH關鍵字是可選項 PRIORY運算符必須放置在連接關係的兩列中某一個的前面。對於節點間的父子關係,PRIOR 運算符在一側表示父節點,在另一側表示子節點,從而確定查找樹結構是的順序是自頂向下還是 自底向上。在連接關係中,除了可以使用列名外,還允許使用列表達式。START WITH 子句爲 可選項,用來標識哪個節點作爲查找樹型結構的根節點。若該子句被省略,則表示所有滿足查詢 條件的行作爲根節點。 完整的例子如SELECT PID,ID,NAME FROM T_WF_ENG_WFKIND START WITH PID =0 CONNECT BY PRIOR ID = PID 以上主要是針對上層對下層的順向遞歸查詢而使用start with ... connect by prior ...這種方式,但有時在需求需要的時候,可能會需要由下層向上層的逆向遞歸查詢,此是語句就有所變化:例如要實現 select * from table where id in ('0','01','0101','0203','0304') ;現在想把0304的上一級03給遞歸出來,0203的上一級02給遞歸出來,而01現在已經是存在的,最高層爲0.而這張table不僅僅這些數據,但我現在只需要('0','01','0101','0203','0304','02','03')這些數據,此時語句可以這樣寫SELECT PID,ID,NAME FROM V_WF_WFKIND_TREE WHERE ID IN (SELECT DISTINCT(ID) ID FROM V_WF_WFKIND_TREE CONNECT BY PRIOR PID = ID START WITH ID IN ('0','01','0101','0203','0304') ); 其中START WITH ID IN裏面的值也可以替換SELECT 子查詢語句. 注意由上層向下層遞歸與下層向上層遞歸的區別在於START WITH...CONNECT BY PRIOR...的先後順序以及 ID = PID 和 PID = ID 的微小變化! ============================================================= connect by prior start with 經常會被用到一個表中存在遞歸關係的時候。比如我們經常會將一個比較複雜的目錄樹存儲到一個表中。或者將一些部門存儲到一個表中,而這些部門互相有隸屬關係。這個時候你就會用到connect by prior start with。 典型的使用方法就是: select * from table connect by prior cur_id=parent_id start with cur_id=??? 例如: a b 1 0 2 1 3 1 4 2 5 3 如果想查找a=2及其下面的所有數據,則: select * from table connect by prior a=b start with a=2 a b 2 1 4 2 這些只是基礎,皮毛。其實只要你靈活的構造查詢語句。可以得出意想不到的結果。比如生成樹每一個路徑。 但是這些記錄組成的樹必須正常纔可以。如果有互爲父子的情況,就會出現循環錯誤! select * from tb_cus_area_cde --子取父 select * from tb_cus_area_cde a CONNECT BY PRIOR a.c_snr_area=a.c_area_cde START WITH a.c_area_cde='1040101' --父取子 select * from tb_cus_area_cde a CONNECT BY PRIOR a.c_area_cde=a.c_snr_area START WITH a.c_snr_area is null 注意:在用這個函數的時候,statement的參數要用 ResultSet.TYPE_SCROLL_INSENSITIVE 而不能用 ResultSet.TYPE_SCROLL_SENSITIVE,在這裏再把這兩個之間的區別講講: 1.TYPE_FORWORD_ONLY,只可向前滾動;
2.TYPE_SCROLL_INSENSITIVE,雙向滾動,但不及時更新,就是如果數據庫裏的數據修改過,並不在ResultSet中反應出來。
3.TYPE_SCROLL_SENSITIVE,雙向滾動,並及時跟蹤數據庫的更新,以便更改ResultSet中的數據
====================================== 10g樹形查詢特性CONNECT_BY_ISCYCLE 在10g中Oracle提供了新的僞列:CONNECT_BY_ISCYCLE,通過這個僞列,可以判斷是否在樹形查詢的過程中構成了循環,這個僞列只是在CONNECT BY NOCYCLE方式下有效。 這一篇描述一下解決問題的思路。 CONNECT_BY_ISCYCLE的實現和前面兩篇文章中CONNECT_BY_ROOT和CONNECT_BY_ISLEAF的實現完全不同。 因爲要實現CONNECT_BY_ISCYCLE,就必須先實現CONNECT BY NOCYCLE,而在9i中是沒有方法實現這個功能的。 也就是說,首先要實現自己的樹形查詢的功能,而僅這第一點,就是一個異常困難的問題,何況後面還要實現NOCYCLE,最後再加上一個ISCYCLE的判斷。 所以總的來說,這個功能的實現比前面兩個功能要複雜得多。由於樹形查詢的LEVEL是不固定的,所以採用鏈接的方式實現,基本上是不現實的。換句話說,用純SQL的方式來實現樹形查詢的功能基本上不可行。而爲了解決這個功能,只能通過PL/SQL配合SQL來實現。 仍然是首先構造一個例子: SQL> CREATE TABLE T_TREE (ID NUMBER, FATHER_ID NUMBER, NAME VARCHAR2(30)); 表已創建。 SQL> INSERT INTO T_TREE VALUES (1, 0, 'A'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (2, 1, 'BC'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (3, 1, 'DE'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (4, 1, 'FG'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (5, 2, 'HIJ'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (6, 4, 'KLM'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (7, 6, 'NOPQ'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (0, 0, 'ROOT'); 已創建 1 行。 SQL> INSERT INTO T_TREE VALUES (4, 7, 'FG'); 已創建 1 行。 SQL> COMMIT; 提交完成。 SQL> SELECT * FROM T_TREE; ID FATHER_ID NAME ---------- ---------- ------------------------------ 1 0 A 2 1 BC 3 1 DE 4 1 FG 5 2 HIJ 6 4 KLM 7 6 NOPQ 0 0 ROOT 4 7 FG 已選擇9行。 上面構造了兩種樹形查詢循環的情況,一種是當前記錄的自循環,另一種是樹形查詢的某個子節點是當前節點的祖先節點,從而構成了循環。在這個例子中,記錄ID爲0和ID爲4且FATHER_ID等於7的兩條記錄分別構成了上述的兩種循環的情況。 下面就來看看CONNECT_BY_ISCYCLE和CONNECT BY NOCYCLE的功能: SQL> SELECT * 2 FROM T_TREE 3 START WITH ID = 0 4 CONNECT BY PRIOR ID = FATHER_ID; ERROR: ORA-01436: 用戶數據中的 CONNECT BY 循環 未選定行 SQL> SELECT * 2 FROM T_TREE 3 START WITH ID = 1 4 CONNECT BY PRIOR ID = FATHER_ID; ERROR: ORA-01436: 用戶數據中的 CONNECT BY 循環 未選定行 這就是不使用CONNECT BY NOCYCLE的情況,查詢會報錯,指出樹形查詢中出現循環,在10g中可以使用CONNECT BY NOCYCLE的方式來避免錯誤的產生: SQL> SELECT * 2 FROM T_TREE 3 START WITH ID = 0 4 CONNECT BY NOCYCLE PRIOR ID = FATHER_ID; ID FATHER_ID NAME ---------- ---------- ------------------------------ 0 0 ROOT 1 0 A 2 1 BC 5 2 HIJ 3 1 DE 4 1 FG 6 4 KLM 7 6 NOPQ 已選擇8行。 使用CONNECT BY NOCYCLE,Oracle自動避免循環的產生,將不產生循環的數據查詢出來,下面看看CONNECT_BY_ISCYCLE的功能: SQL> SELECT ID, 2 FATHER_ID, 3 NAME, 4 CONNECT_BY_ISCYCLE CYCLED 5 FROM T_TREE 6 START WITH ID = 0 7 CONNECT BY NOCYCLE PRIOR ID = FATHER_ID; ID FATHER_ID NAME CYCLED ---------- ---------- ------------------------------ ---------- 0 0 ROOT 1 1 0 A 0 2 1 BC 0 5 2 HIJ 0 3 1 DE 0 4 1 FG 0 6 4 KLM 0 7 6 NOPQ 1 已選擇8行。 可以看到,CONNECT_BY_ISCYCLE僞列指出循環在樹形查詢中發生的位置。 爲了實現CONNECT_BY_ISCYCLE就必須先實現CONNECT BY NOCYCLE方式,而這在9i中是沒有現成的辦法的,所以這裏嘗試使用PL/SQL來自己實現樹形查詢的功能。 SQL> CREATE OR REPLACE FUNCTION F_FIND_CHILD(P_VALUE VARCHAR2) RETURN VARCHAR2 AS 2 V_STR VARCHAR2(32767) := '/' || P_VALUE; 3 4 PROCEDURE P_GET_CHILD_STR (P_FATHER IN VARCHAR2, P_STR IN OUT VARCHAR2) AS 5 BEGIN 6 FOR I IN (SELECT ID FROM T_TREE WHERE FATHER_ID = P_FATHER AND FATHER_ID != ID) LOOP 7 IF INSTR(P_STR || '/', '/' || I.ID || '/') = 0 THEN 8 P_STR := P_STR || '/' || I.ID; 9 P_GET_CHILD_STR(I.ID, P_STR); 10 END IF; 11 END LOOP; 12 END; 13 BEGIN 14 P_GET_CHILD_STR(P_VALUE, V_STR); 15 RETURN V_STR; 16 END; 17 / 函數已創建。 構造一個函數,在函數中遞歸調用過程來實現樹形查詢的功能。 下面看看調用這個函數的結果: SQL> SELECT F_FIND_CHILD(0) FROM DUAL; F_FIND_CHILD(0) ------------------------------------------------ /0/1/2/5/3/4/6/7 SQL> SELECT F_FIND_CHILD(2) FROM DUAL; F_FIND_CHILD(2) ------------------------------------------------ /2/5 SQL> SELECT F_FIND_CHILD(4) FROM DUAL; F_FIND_CHILD(4) ------------------------------------------------ /4/6/7 雖然目前存在的問題還有很多,但是已經基本上實現了一個最簡單的NOCYCLE的SYS_CONNECT_BY_PATH的功能。 有了這個函數作爲基礎,就可以逐步的實現最終的目標了。 |