1. 程式人生 > >oracle connect by用法以及with遞迴

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