sql語句遞歸查詢(start with)


在做項目中遇到一個問題,就是同一個表中的數據存在級聯關系,但是只要查出來的末級數據,糾結了好久,好不容易找到了一個博主的分享,在這里做個記錄,也是和大家一起分享學習一下這位大神的技術,共勉

 

寫代碼時碰到要弄清楚Oracle的role之間的傳遞關系,就是有role A的話,可以通過grant A to B,把A賦予給B,又通過grant B to C .那我想知道所有role中,有哪些role具有A的權限.

上網一查發現有個遞歸查詢,不過都講的不是太詳細,而那Oracle整的那用法實在太怪異了,跟我們平時用的SQL差的太遠,所以琢磨了好一陣子腦子才轉過彎呢.

樹形結構

可能一看到遞歸查詢這樣太專業的名詞大家就迷糊了.實際上可以看成有一個樹形結構,然后我們要怎么把所有樹的所有結點查找出來.學數據結構的時候我們知道要遍歷一個樹結構有啥前序遍歷,中序遍歷,后序遍歷.反正挺麻煩的.不像遍歷個數組那么容易的.那實際上在Oracle的一個表中也可以保存樹形結構信息.你要查詢所有的樹節點,自己整個函數或存儲過程去整肯定是超級麻煩的.Oracle提供了一個簡單的機制幫助你.要用到start with ...connect by等關鍵字.先來假定有下面這樣一個簡單的樹形結構存儲在表中.

create table Tree(son char(10), father char(10)); 然后插入些信息變成這樣的表

 

SON            FATHTER

孫子SB         兒子

孫子NB         兒子

兒子             爸爸

爸爸             爺爺

 

很顯然這是一個簡單的樹形結構

                         

   ---------孫子SB

               |              ^

               |              |

爺爺 --> 爸爸 --> 兒子 -->孫子NB

                          

 

遞歸查詢

假如要查詢出以爺爺為根的樹的所有節點值咋整呢 ?如果數據少多來幾個where嵌套就行.但要是樹層次有幾百那會搞死人了啊.於是我們就用Oracle提供的遞歸查詢.先看下SQL的寫法然后再講解

SELECT  son FROM tree

START WITH father = '爺爺'

CONNECT BY PRIOR son = father;

返回的結果為 爸爸 兒子 孫子NB 孫子SB

代碼看起來很短,但是極為怪異,腦子半天都不容易轉過彎呢.實際上我們不把這個SQL語句跟一般的SQL類比,而把它當作給一些函數指定一些參數的賦值語句才更容易理解.

那怎么來理解上面的SQL呢?

首先把SELECT son FROM tree還是看成一般sql一樣,就是要查找son這一列的信息.而把以START WITH開頭的后面所有東東看成一個where限制條件.其中START WITH 是指定

樹的根,這里指定的根是 '爺爺',實際上你還可以指定多個根的,比如 father in ('爺爺', '爸爸') .

而CONNECT BY PRIOR son = father相當於表明在遞歸的過程中,查找到的樹中其它節點接着又作為根結點.然后繼續遞歸. 反正看這sql語句前先想下樹形結構,然后想下一般編程語言中的遞歸函數.再來看時就容易理解點.實際上我覺得Oracle這樣設計不太好.如果用戶只是簡單的指定的一個根節點然后知道樹中其他節點信息.那么就直接用START WITH指定根就行了.CONNECT BY PRIOR顯得有點多余,可以不用用戶去指定.當作一個默認值,只有需要其他一些更復雜的操作時才讓用戶明確指定.這樣就不容易誤導人了.

為了便於理解可以可以CONNECT BY那一行當作多余的,只記住要查詢的列名放前面,根列名放等號后面就行.這樣只要指定樹的根結點就比較好理解了.

 

start with ,connect by prior其他變形

上面講的用START WITH 指定樹的根,然后用CONNECT BY指定遞歸條件.是最簡單的也是最常用的形式.但實際上還有些變形.

1.START WITH 可以省略

比如

SELECT son FROM tree

CONNECT BY PRIOR son = father;

此時不指定樹的根的話,就默認把Tree整個表中的數據從頭到尾遍歷一次,每一個數據做一次根,然后遍歷樹中其他節點信息.

在這個例子中,上面的SQL等價於

SELECT son FROM tree

START WITH father IN (爺爺,爸爸,兒子,孫子NB,孫子SB)

CONNECT BY PRIOR son = father;

那查詢到的結果如下,有很多重復信息的

爸爸,兒子,孫子NB,孫子SB    兒子,孫子NB,孫子SB    孫子NB,孫子SB

 

2.START WITH 與CONNECT BY PRIOR位置可互換

 

SELECT son FROM tree

CONNECT BY PRIOR son = father

START WITH father = '爺爺';

這語句與最開頭那個是等價的

 

3.nocycle關鍵字

我們知道標准的樹結構中是不會有環的,但表中的樹形結構不是標准的,有可能導致環的出現

比如

             ---------孫子SB

             |              ^

             |              |

爺爺 --> 爸爸 --> 兒子 -->孫子NB

哎在這里想用線條整個箭頭出來真他媽麻煩啊.我又有點懶不想用其他畫圖工具啥的啊.反正假設兒子的兒子是孫子SB ,而孫子SB的兒子是爸爸.這樣就形成一個環了.

當然在Oracle中的role是禁止出現循環的.比如你grant A to B ,grant B to C .再來個grant C to A會出錯的.

假如有個上面的環,在再使用開始的遞歸查詢語言會出錯.得用nocycle關鍵字指定忽略環.

SELECT son FROM tree

START WITH father = '爺爺'

CONNECT BY NOCYCLE PRIOR son = father;

此時結果是

爸爸 兒子 孫子NB

你會注意到由於忽略了環,所以孫子SB的信息也被忽略掉了.

 

4. connect by prior 后面條件順序的改變 (???)

SELECT son FROM tree

START WITH father = '爺爺'

CONNECT BY PRIOR son = father;

這是開頭的寫法,但實際上也可以寫成father = son倒過來寫.有人說沒倒過來是從上到下,從根往下.如果倒過來則是從下到上.哎不過我測試了下發現不是那么回事.結果也有點亂七八糟的.沒想明白是啥規律.反正還有待研究啊

 

5.還可以加where條件

我上面說了可以把start with ,connect 假裝看成where 條件一樣.所以在這個sql語句其他地方還可以加其他where 語句,可以看成與遞歸查詢無關.只是對整個結果起過濾作用

比如

SELECT son FROM tree  WHERE son = '孫子SB'

START WITH father = '爺爺'

CONNECT BY PRIOR son = father;

 

當然你不能在最后部分加where,不能在connect by最后面再加.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM