Oracle使用connect by進行級聯查詢 樹型菜單(轉)javascript
connect by能夠用於級聯查詢,經常使用於對具備樹狀結構的記錄查詢某一節點的全部子孫節點或全部祖輩節點。 java
來看一個示例,現假設咱們擁有一個菜單表t_menu,其中只有三個字段:id、name和parent_id。它們是具備父子關係的,最頂級的菜單對應的parent_id爲0。現假設咱們擁有以下記錄:sql
idapp |
name優化 |
parent_idspa |
1orm |
菜單01排序 |
0ip |
2ci |
菜單02 |
0 |
3 |
菜單03 |
0 |
4 |
菜單0101 |
1 |
5 |
菜單0102 |
1 |
6 |
菜單0103 |
1 |
7 |
菜單010101 |
4 |
8 |
菜單010201 |
5 |
9 |
菜單010301 |
6 |
10 |
菜單0201 |
2 |
11 |
菜單0202 |
2 |
12 |
菜單020101 |
10 |
13 |
菜單020102 |
10 |
14 |
菜單020103 |
10 |
15 |
菜單0301 |
3 |
16 |
菜單0302 |
3 |
17 |
菜單030201 |
16 |
18 |
菜單030202 |
16 |
19 |
菜單030203 |
16 |
若是這個時候咱們須要查詢「菜單01」以及其下全部的子孫菜單應該怎麼辦呢?若是使用connect by的話這將會很是簡單,使用以下SQL語句就能夠達到對應的效果。
connect by是須要跟start with一塊兒使用的。connect by後跟的是鏈接條件,在connect by後接的條件一般都須要使用關鍵字「prior」,能夠簡單的把它理解爲上一級,因此上述例子中「connect by parent_id=prior id」就表示鏈接條件爲parent_id等於上級的id,查找到下一級記錄後又會找parent_id等於下一級記錄的id的記錄,而prior對應的最頂層的記錄就是經過start with來肯定的,start with後接對應的篩選條件,表示最頂層的記錄是哪些,最頂層的記錄能夠有多個,好比我想查找「菜單01」下的子孫菜單,可是不包括「菜單01」自己,那麼我就可使用以下的SQL語句進行查找,此時「start with parent_id=1」對應的記錄就會有多條。
對應的結果爲:
id |
name |
parent_id |
4 |
菜單0101 |
1 |
5 |
菜單0102 |
1 |
6 |
菜單0103 |
1 |
7 |
菜單010101 |
4 |
8 |
菜單010201 |
5 |
9 |
菜單010301 |
6 |
此外,若是咱們想查找「菜單010101」對應的祖輩菜單也很是簡單,以下SQL就能夠實現該功能,即從「菜單010101」的父菜單(對應id爲4)開始查找。
對應的結果爲:
id |
name |
parent_id |
1 |
菜單01 |
0 |
4 |
菜單0101 |
1 |
使用connect by時咱們可使用內置的相似於rownum的一個叫level的僞列,該列表示當前記錄相對於start with記錄的一個層級,start with記錄的level爲1。如上面的兩條SQL語句,若是加上level的話對應的結果將是這樣的。
對應的結果爲:
level |
id |
name |
parent_id |
1 |
4 |
菜單0101 |
1 |
1 |
5 |
菜單0102 |
1 |
1 |
6 |
菜單0103 |
1 |
2 |
7 |
菜單010101 |
4 |
2 |
8 |
菜單010201 |
5 |
2 |
9 |
菜單010301 |
6 |
對應的結果爲:
level |
id |
name |
parent_id |
2 |
1 |
菜單01 |
0 |
1 |
4 |
菜單0101 |
1 |
有了level後,咱們就能夠對查詢的level作一個限制,好比只查從最頂層開始向下兩級的菜單。
從上述SQL咱們能夠看到where條件是直接跟在from以後的,使用connect by時咱們的where條件不是在connect by以前對數據進行過濾的,而是在connect by以後纔對全部的數據進行過濾的,這一點跟使用分組語句group by時是不同的,group by是先經過where對須要分組的數據進行過濾後再經過group by來分組的。
若是咱們的記錄中存在循環的父子關係,則使用connect by進行查詢時會拋出異常,如A->B、B->C、C->A這樣的記錄。解決辦法是在connect by語句後加上「nocycle」,表示不循環查詢,如:
使用nocycle後對於A->B、B->C、C->A這樣的記錄會經過查詢B,而後經過B查詢C,再經過C查詢A時發現已經循環了,就再也不查詢了,即在C這條記錄這裏循環了。在對存在循環記錄的查詢中咱們也能夠經過「connect_by_iscycle」找到是哪一條記錄循環了,「connect_by_iscycle」也是一個僞列,其必須和nocycle一塊兒使用。僞列「connect_by_iscycle」對應的值有0和1,若是某一條記錄的connect_by_iscycle對應的值爲1則表示從該條記錄這裏開始循環了。以下是一個使用connect_by_iscycle的示例。
connect_by_isleaf也是一個僞列,其表示對應的記錄是不是一個葉子節點,即在進行connect by時不能經過該記錄找到下一條記錄。其對應的值有0和1,0表示非葉子節點,1表示是葉子節點。如我只想找出是葉子節點的菜單時對應的SQL能夠這樣寫:
connect_by_root表示根節點,即某一條記錄所對應的最頂級的記錄,其用法跟prior相似,後面也須要跟一個字段名。以下面示例能夠查詢全部葉子節點菜單的最頂級菜單和上級菜單的名稱。
對應上表的記錄,在上述SQL中查詢出來的結果應該以下所示:
root_name |
prior_name |
id |
name |
parent_id |
菜單01 |
菜單0101 |
7 |
菜單010101 |
4 |
菜單01 |
菜單0102 |
8 |
菜單010201 |
5 |
菜單01 |
菜單0103 |
9 |
菜單010301 |
6 |
菜單02 |
菜單02 |
11 |
菜單0202 |
2 |
菜單02 |
菜單0201 |
12 |
菜單020101 |
10 |
菜單02 |
菜單0201 |
13 |
菜單020102 |
10 |
菜單02 |
菜單0201 |
14 |
菜單020103 |
10 |
菜單03 |
菜單03 |
15 |
菜單0301 |
3 |
菜單03 |
菜單0302 |
17 |
菜單030201 |
16 |
菜單03 |
菜單0302 |
18 |
菜單030202 |
16 |
菜單03 |
菜單0302 |
19 |
菜單030203 |
16 |
sys_connect_by_path(column,delimiter)能夠用來展現以指定column和分隔符delimiter表示從根節點到當前節點的路徑。如下SQL用來查詢id爲2的菜單下葉子節點的信息,包括以字段name和分隔符「>」表示的其對應的根節點的路徑。
對應結果以下所示:
connect_path |
id |
name |
parent_id |
>菜單02>菜單0202 |
11 |
菜單0202 |
2 |
>菜單02>菜單0202>菜單020101 |
12 |
菜單020101 |
10 |
>菜單02>菜單0202>菜單020102 |
13 |
菜單020102 |
10 |
>菜單02>菜單0202>菜單020103 |
14 |
菜單020103 |
10 |
可使用order by對connect by以後的結果進行排序,此時order by需放在最末端,而不像where篩選那樣直接定義在from以後。如需對connect by以後的結果按id進行排序,則可使用以下SQL語句:
除了傳統的針對查詢結果的排序外,connect by語句還支持對同一父節點下的子節點進行排序,這是經過order siblings by來定義的。如咱們須要查詢id爲2的菜單下的全部子孫菜單,而後對具備同一父節點的菜單按id進行倒序排列,則咱們的SQL語句能夠以下定義:
對應的結果會是這樣子:
id |
name |
parent_id |
2 |
菜單02 |
0 |
11 |
菜單0202 |
2 |
10 |
菜單0201 |
2 |
14 |
菜單020103 |
10 |
13 |
菜單020102 |
10 |
12 |
菜單020101 |
10 |
如上表所示,咱們能夠看到「菜單0201」和「菜單0202」具備相同的父節點「菜單02」,它們按照id進行倒序排列,全部「菜單0202」在「菜單0201」以前,一樣「菜單020101」、「菜單020102」和「菜單020103」具備相同的父節點「菜單0201」,因此它們也是按照id的倒序排列。
有這麼一個需求:表A表示分類,表B表示任務模板,A與B是一對多的關係,每個任務模板都屬於一個特定的分類,在表B中用字段a表示所屬的分類。分類存在父子關係,子分類的parent_id對應父分類的id。現假設須要統計id爲1的分類及其子分類下存在的任務模板數量。對應SQL以下:
現假設擁有另一個表C,其表示任務實例,一個任務模板B能夠擁有n個任務實例B,即B跟C之間是一對多的關係。任務實例C經過字段b關聯任務模板B,另外任務實例C擁有一個字段status表示任務實例的具體狀態。現假設須要統計id爲1的分類及其子分類下各狀態的任務實例數量。對應SQL以下:
在A表數據量1000,B表數據量20000,C表數據量5000,id爲1的分類下屬的子孫分類數量爲100的狀況下第一條SQL的查詢速度能夠在0.1秒左右完成,而第二條SQL須要將近10秒才能完成。把查詢id爲1的分類下子孫分類的id的SQL語句「selectidfrom A connectbypriorid=parent_id startwithid=1」單獨查詢的速度也能夠在0.1秒內完成。一般對於這種數量級別的三表查詢都是能夠在0.1秒內完成的,爲此心想第二條SQL應該是受了子查詢中connect by的影響。後來決定把分類的子查詢直接做爲B的in條件進行查詢,以下所示:
其查詢效果是同樣的,心想應該仍是connect by影響到了,既然單獨使用connect by查詢id爲1的分類的子孫分類的id只須要不到0.1秒,那何不在程序裏面先將id爲1的分類的子孫分類id查詢出來,再做爲B、C聯合查詢的in條件,如:
結果查詢結果也能夠在0.1秒內完成。