oracle connect by用法以及with遞迴
環境:11g 準備: 在oracle中start with connect by (prior) 用來對樹形結構的資料進行查詢。其中start with conditon 給出的是資料搜尋範圍, connect by後面給出了遞迴查詢的條件; 涉及的偽列及函式: – connect_by_isleaf 偽列 – connect_by_root()獲取根節點 – sys_connect_by_path(a,b) 顯示分層路徑 – connect_by_iscycle 是否迴圈
語法: [start with] connect by [nocycle] prior 條件 [and]
-- 表結構 drop table menu; create table menu( mid varchar2(64) not null, parent_id varchar2(64) not null, mname varchar2(100) not null, mdepth number(2) not null, primary key (mid) ); -- 初始化資料 -- 頂級選單 insert into menu values ('100000', '0', '頂級選單1', 1); insert into menu values ('200000', '0', '頂級選單2', 1); insert into menu values ('300000', '0', '頂級選單3', 1); -- 父級選單 -- 頂級選單1 直接子選單 insert into menu values ('110000', '100000', '選單11', 2); insert into menu values ('120000', '100000', '選單12', 2); insert into menu values ('130000', '100000', '選單13', 2); insert into menu values ('140000', '100000', '選單14', 2); -- 頂級選單2 直接子選單 insert into menu values ('210000', '200000', '選單21', 2); insert into menu values ('220000', '200000', '選單22', 2); insert into menu values ('230000', '200000', '選單23', 2); -- 頂級選單3 直接子選單 insert into menu values ('310000', '300000', '選單31', 2); -- 選單13 直接子選單 insert into menu values ('131000', '130000', '選單131', 3); insert into menu values ('132000', '130000', '選單132', 3); insert into menu values ('133000', '130000', '選單133', 3); -- 選單132 直接子選單 insert into menu values ('132100', '132000', '選單1321', 4); insert into menu values ('132200', '132000', '選單1332', 4);
測試:
--看prior修飾的物件,如果是父id,則獲取的是父節點資料,反之獲取的是子節點資料 --1找指定節點的所有父節點(直接或間接) select * from menu aa start with aa.mid = '130000' connect by aa.mid = prior aa.parent_id; --或者 select * from menu aa start with aa.mid = '130000' connect by prior aa.parent_id = aa.mid; --如果不用樹查詢,如何實現呢? --需要知道查詢的次數,然後不斷的union all,比較麻煩 --或者更改表設計 select aa.* from menu aa where aa.mid='130000' union all select b.* from menu b where b.mid=(select aa.parent_id from menu aa where aa.mid='130000'); --2找到指定節點的所有子節點(直接或間接) select * from menu aa start with aa.mid = '130000' connect by prior aa.mid = aa.parent_id; --或者 select aa.*, level 層級號 from menu aa start with aa.mid = '130000' connect by aa.parent_id = prior aa.mid; --3.只獲取指定節點的直接父節點 select * from menu aa where aa.mid='130000'; --4.只獲取指定節點的直接位元組點,利用level限制 select * from (select aa.*, level lev from menu aa start with aa.mid = '130000' connect by aa.parent_id = prior aa.mid) tt where tt.lev = 2; --注意:在外面限制,裡面限制沒用 --或者 select * from menu aa where aa.parent_id='130000'; --4.1 獲取不是'130000'分支的樹 select aa.*, level 層級號 from menu aa connect by aa.parent_id = prior aa.mid and aa.mid != '130000' and aa.parent_id; --5.獲取指定選單的子選單的個數,包括自已 select count(1) from ( select * from menu aa start with aa.mid='130000' connect by prior aa.mid = aa.parent_id ); --5.1 獲取每一個選單的子選單的個數(包括自已,包括直接或間接) --思路: 先求出每個選單的父選單,再根據父選單分組,即可得到每個選單的子選單個數 select aa.mid,max(aa.mname),count(1) 子選單個數 from menu aa group by aa.mid connect by aa.mid=prior aa.parent_id order by aa.mid; --如果要不包括自已 select aa.mid,max(aa.mname),count(1)-1 子選單個數 from menu aa group by aa.mid connect by aa.mid=prior aa.parent_id order by aa.mid; --6 形象展示選單結構 --展示 130000的結構 -- connect_by_isleaf 偽列 -- connect_by_root()獲取根節點 -- sys_connect_by_path(a,b) 顯示分層路徑 -- siblings by,能保持兄弟關係(層次),優先層次顯示,同一層次的再按照字典順序排序。 select aa.mid, lpad('|-', level * 2, ' ') || aa.mname, aa.parent_id, decode(connect_by_isleaf, 0, '根節點', 1, ' 葉子節點') isleaf, connect_by_root(aa.mname) 根節點, sys_connect_by_path(aa.mname,'=>') 分層路徑 from menu aa start with aa.mid = '130000' connect by aa.parent_id = prior aa.mid order siblings by aa.mname; --6.1展示 所有頂級節點的結構 select * from ( select aa.mid, lpad('|-', level * 2, ' ') || aa.mname 選單結構, aa.parent_id, decode(connect_by_isleaf, 0, '根節點', 1, ' 葉子節點') isleaf, connect_by_root(aa.mname) 根節點, connect_by_root(aa.mid) rootid from menu aa connect by aa.parent_id = prior aa.mid) tt where tt.rootid in(select a.mid from menu a where a.parent_id='0') order by tt.rootid,tt.mid;
--6.2 如果要去掉某個分支呢? 先把全部的樹查詢出來,再minus掉指定分支; --7. 獲取指定範圍的樹形資料 --比如獲取20部門的 頂級節點的子節點,已知 mgr=null在部門10 select * from emp e where e.deptno=20 start with e.mgr is null connect by prior e.empno=e.mgr; --發現竟然有資料,說明這個查詢肯定是不對的,因為此時的where作用的是整個樹形的查詢結果,而不是emp --所有在查詢的一開始就要限制範圍 --正確寫法 select * from (select * from emp e where e.deptno = 20) e start with e.mgr is null connect by prior e.empno = e.mgr; 8.where過濾的問題,where的過濾條件針對的是樹查詢結果進行過濾的; 注意:這說的是where的過濾條件(帶常量的),不是連線條件!!! select * from menu aa where aa.mid!='130000' start with aa.mid = '130000' connect by prior aa.mid = aa.parent_id;
這個時候有5條記錄;相當於把’130000’的直接父節點的記錄刪除掉了; 從執行計劃可以看到,是在最後把結果求出來後,再過濾的;
--如果把條件放在connect by後面呢?
select *
from menu aa
start with aa.mid = '130000'
connect by prior aa.mid = aa.parent_id
and aa.mid!='130000';
發現結果沒有變化,為啥呢? 猜測是在遞迴迴圈的過程中,無法過濾start with的mid節點條件; 導致過濾無效;下面的列子會說明的;
再看: select * from menu aa start with aa.mid = ‘130000’ connect by prior aa.mid = aa.parent_id and aa.mid!=‘132000’; 這個是在遞迴迴圈過程中的過濾條件,在把mid!='132000’時,源頭132000沒了.那麼涉及的parent_id自然沒有132000; 等價於把的mid為132000以及parent_id為132000的全部去掉; 這種在迴圈的過程中進行過濾的,過濾效果相對於where過濾條件,過濾的更多;
select * from emp e where e.deptno=20 start with e.mgr is null connect by prior e.empno=e.mgr; select * from emp e start with e.mgr is null connect by prior e.empno=e.mgr and e.deptno=20; 也可以看到,connect by 後的and條件無法過濾掉start with的開始條件;
--9.cconnect by不要prior呢,可以用來建立資料;
select * from menu aa
start with aa.mid='130000'
connect by aa.mid = aa.parent_id;
--此時等價於
select * from menu aa
where aa.mid='130000';
select rownum,level from dual connect by rownum<500;
對於上面的遞迴查詢,在11g之前只能用start with ... connnect by prior來實現,
從版本11GR2開始,ORACLE支援遞迴的WITH, 即允許在WITH子查詢的定義中對自身引用。
其他資料庫如DB2, Firebird, Microsoft SQL Server, PostgreSQL 都先於ORACLE支援這一特性;
語法:
WITH
① query_name ([c_alias [, c_alias]...])
② AS (subquery)
③ [search_clause]
④ [cycle_clause]
⑤ [,query_name ([c_alias [, c_alias]...]) AS (subquery) [search_clause] [cycle_clause]]...
①這是子查詢的名稱,和以往不同的是,必須在括號中把這個子查詢的所有列名寫出來。
②AS後面的subquery就是查詢語句,遞迴部分就寫在這裡。
③遍歷順序子句,可以指定深度優先或廣度優先遍歷順序。
④迴圈子句,用於中止遍歷中出現的死迴圈。
⑤如果還有其他遞迴子查詢,定義同上。
SELECT empno,
ename,
job,
mgr,
deptno,
level,
SYS_CONNECT_BY_PATH(ename, '\') AS path,
CONNECT_BY_ROOT(ename) AS top_manager
FROM EMP
START WITH mgr IS NULL
CONNECT BY PRIOR empno = mgr;
等價:
WITH T(empno, ename, job, mgr, deptno, the_level, path,top_manager) AS ( ---- 必須把結構寫出來
SELECT empno, ename, job, mgr, deptno ---- 先寫錨點查詢,用START WITH的條件
,1 AS the_level ---- 遞迴起點,第一層
,'\'||ename ---- 路徑的第一截
,ename AS top_manager ---- 原來的CONNECT_BY_ROOT
FROM EMP
WHERE mgr IS NULL ---- 原來的START WITH條件
UNION ALL ---- 下面是遞迴部分
SELECT e.empno, e.ename, e.job, e.mgr, e.deptno ---- 要加入的新一層資料,來自要遍歷的emp表
,1 + t.the_level ---- 遞迴層次,在原來的基礎上加1。這相當於CONNECT BY查詢中的LEVEL偽列
,t.path||'\'||e.ename ---- 把新的一截路徑拼上去
,t.top_manager ---- 直接繼承原來的資料,因為每個路徑的根節點只有一個
FROM t, emp e ---- 典型寫法,把子查詢本身和要遍歷的表作一個連線
WHERE t.empno = e.mgr ---- 原來的CONNECT BY條件
) ---- WITH定義結束
SELECT * FROM T
;
with迴圈參考:https://blog.csdn.net/cacasi2568/article/details/55224422
好,用法就介紹到這了,不知道內有沒收穫呀 下一篇將介紹connect by的優化,這個是個難點;
end by ysy