樹概念及使用connect by進行級聯查詢

来源:https://www.cnblogs.com/outmanxiaozhou/archive/2018/12/12/10109128.html
-Advertisement-
Play Games

樹 樹,大家都見過,以這種形式的數據關係,就是樹。下麵看一張圖,瞭解什麼是根節點(樹幹)、節點或分叉、葉(葉節點) connect by 級聯查詢 connect by可以用於級聯查詢,常用於對具有樹狀結構的記錄查詢某一節點的所有子孫節點或所有祖輩節點。 來看一個示例,現假設我們擁有一個菜單表t_m ...


樹,大家都見過,以這種形式的數據關係,就是樹。下麵看一張圖,瞭解什麼是根節點(樹幹)、節點或分叉、葉(葉節點)

 

connect by 級聯查詢

connect by可以用於級聯查詢,常用於對具有樹狀結構的記錄查詢某一節點的所有子孫節點或所有祖輩節點。

來看一個示例,現假設我們擁有一個菜單表t_menu,其中只有三個欄位:id、name和parent_id。它們是具有父子關係的,最頂級的菜單對應的parent_id為0。現假設我們擁有如下記錄:

id

name

parent_id

1

菜單01

0

2

菜單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語句就可以達到對應的效果。

Sql代碼:  select * from t_menu connect by parent_id=prior id start with id=1;  

       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”對應的記錄就會有多條。

Sql代碼:  select * from t_menu connect by parent_id=prior id 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)開始查找。

Sql代碼:  select * from t_menu connect by id=prior parent_id start with id=4;  

       對應的結果為:

id

name

parent_id

1

菜單01

0

4

菜單0101

1

 

level

       使用connect by時我們可以使用內置的類似於rownum的一個叫level的偽列,該列表示當前記錄相對於start with記錄的一個層級,start with記錄的level為1。如上面的兩條SQL語句,如果加上level的話對應的結果將是這樣的。

Sql代碼:  select level,t.* from t_menu t connect by parent_id=prior id start with parent_id=1;  

       對應的結果為:

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

 

Sql代碼:  select level,t.* from t_menu t connect by id=prior parent_id start with id=4;  

       對應的結果為:

level

id

name

parent_id

2

1

菜單01

0

1

4

菜單0101

1

 

       有了level後,我們就可以對查詢的level做一個限制,比如只查從最頂層開始向下兩級的菜單。

Sql代碼:  select level,t.* from t_menu t where level<3 connect by prior id= parent_id start with parent_id=0;  

       從上述SQL我們可以看到where條件是直接跟在from之後的,使用connect by時我們的where條件不是在connect by之前對數據進行過濾的,而是在connect by之後才對所有的數據進行過濾的,這一點跟使用分組語句group by時是不一樣的,group by是先通過where對需要分組的數據進行過濾後再通過group by來分組的。

 

nocycle和connect_by_iscycle

       如果我們的記錄中存在迴圈的父子關係,則使用connect by進行查詢時會拋出異常,如A->B、B->C、C->A這樣的記錄。解決辦法是在connect by語句後加上“nocycle”,表示不迴圈查詢,如:

Sql代碼:  select * from t_menu connect by nocycle prior id=parent_id start with parent_id=0;  

       使用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的示例。

Sql代碼:  select connect_by_iscycle,t.* from t_menu t connect by nocycle prior id=parent_id start with parent_id=0;  

connect_by_isleaf

       connect_by_isleaf也是一個偽列,其表示對應的記錄是否是一個葉子節點,即在進行connect by時不能通過該記錄找到下一條記錄。其對應的值有0和1,0表示非葉子節點,1表示是葉子節點。如我只想找出是葉子節點的菜單時對應的SQL可以這樣寫:

Sql代碼:  select connect_by_isleaf,t.* from t_menu t where connect_by_isleaf=1 connect by prior id=parent_id start with parent_id=0;  

connect_by_root

       connect_by_root表示根節點,即某一條記錄所對應的最頂級的記錄,其用法跟prior類似,後面也需要跟一個欄位名。如下麵示例可以查詢所有葉子節點菜單的最頂級菜單和上級菜單的名稱。

Sql代碼:  

select connect_by_root name as root_name, prior name as prior_name,t.* from t_menu t where connect_by_isleaf=1 connect by prior id=parent_id start with parent_id=0;  

       對應上表的記錄,在上述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

       sys_connect_by_path(column,delimiter)可以用來展示以指定column和分隔符delimiter表示從根節點到當前節點的路徑。以下SQL用來查詢id為2的菜單下葉子節點的信息,包括以欄位name和分隔符“>”表示的其對應的根節點的路徑。

Sql代碼:  select sys_connect_by_path(name, '>') as connect_path,t.* from t_menu t where connect_by_isleaf=1 connect by prior id=parent_id start with id=2;  

       對應結果如下所示:

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

       可以使用order by對connect by之後的結果進行排序,此時order by需放在最末端,而不像where篩選那樣直接定義在from之後。如需對connect by之後的結果按id進行排序,則可以使用如下SQL語句:

Sql代碼:  select t.* from t_menu t connect by parent_id=prior id start with parent_id=0 order by id;  

       除了傳統的針對查詢結果的排序外,connect by語句還支持對同一父節點下的子節點進行排序,這是通過order siblings by來定義的。如我們需要查詢id為2的菜單下的所有子孫菜單,然後對具有同一父節點的菜單按id進行倒序排列,則我們的SQL語句可以如下定義:

Sql代碼:  select t.* from t_menu t connect by parent_id=prior id start with id=2 order siblings by id desc;  

       對應的結果會是這樣子:

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的倒序排列。

一次針對connect by的查詢優化

       有這麼一個需求:表A表示分類,表B表示任務模板,A與B是一對多的關係,每一個任務模板都屬於一個特定的分類,在表B中用欄位a表示所屬的分類。分類存在父子關係,子分類的parent_id對應父分類的id。現假設需要統計id為1的分類及其子分類下存在的任務模板數量。對應SQL如下:

Sql代碼:  select count(1) from B b,(select id from A connect by prior id=parent_id start with id=1) a where a.id=b.a;  

       現假設擁有另外一個表C,其表示任務實例,一個任務模板B可以擁有n個任務實例B,即B跟C之間是一對多的關係。任務實例C通過欄位b關聯任務模板B,另外任務實例C擁有一個欄位status表示任務實例的具體狀態。現假設需要統計id為1的分類及其子分類下各狀態的任務實例數量。對應SQL如下:

Sql代碼:  select c.status,count(1) from B b,(select id from A connect by prior id=parent_id start with id=1) a, C c where a.id=b.a and b.id=c.b group by c.status;  

       在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條件進行查詢,如下所示:

Sql代碼:  select c.status,count(1) from B b, C c where b.a in(select id from A connect by prior id=parent_id start with id=1) and b.id=c.b group by c.status;  

       其查詢效果是一樣的,心想應該還是connect by影響到了,既然單獨使用connect by查詢id為1的分類的子孫分類的id只需要不到0.1秒,那何不在程式裡面先將id為1的分類的子孫分類id查詢出來,再作為B、C聯合查詢的in條件,如:

Sql代碼:  select c.status,count(1) from B b, C c where b.a in(...) and b.id=c.b group by c.status;   

       結果查詢結果也可以在0.1秒內完成。

思考

假設需要求菜單parentId為16的數據到菜單03的路徑,用下麵代碼

       select *,sys_connect_by_path(name,'/') from t_menu where parent_id = 16 start with  id is not null connect by  prior id = parent_id

       得出的是每個節點往下的路徑。

  把prior 放在parent_id 前面,可以得出全路徑,但是不好提取到根的路徑。而且如果資料庫很大, start with 用 is not null  會很慢。

  如果這樣操作:

複製代碼
select * from t_menu t
left join (
select connect_by_root id as id ,sys_connect_by_path(name,'/') path
from t_menu
where id=3
start with (select id from t_menu where parent_id =16)
connect by id =prior parent_id) t1
on t.id=t1.id
複製代碼

用connect_by_root 來取出path的actul id,再和原表左聯。會不會好一點?


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.排序概述 2.排序分類 3.WritableComparable案例 這個文件,是大數據-Hadoop生態(12)-Hadoop序列化和源碼追蹤的輸出文件,可以看到,文件根據key,也就是手機號進行了字典排序 欄位含義分別為手機號,上行流量,下行流量,總流量 需求是根據總流量進行排序 Bean對 ...
  • 登錄之後使用如下命令: 還有不知是不是因為mysql版本問題,一開始設置的密碼,使用navicat進行連接測試的時候,報錯: 解決辦法,就是使用如下語句再進行一次密碼修改: ...
  • 遷移主要是通過Navicat工具來實現的。遷移工具的選定在此不討論。 遷移前準備 1.提前通知DBA\SA\BI等,並確認發佈計劃及資料庫遷移方案。 2.梳理出SQL Server DB 中影響業務的Job(遷移的過程中要關閉)、DB LinkServer、相關賬號。 3.模擬數據遷移,評估出相關精 ...
  • 今天自學完了大數據方面生態系統的一些核心技術,在這裡整理一下學過的知識算是複習,也方便初學者參考 ...
  • 一. 複製環境準備 1.1 主庫環境(172.168.18.201) 環境 說明 1.2 從庫環境(172.168.18.203) 環境 說明 二. 複製配置 2.1 主庫環境密碼設置 在201主庫上,找到redis目錄下的redis.conf文件,打開文件找到requirepass,在500行,下 ...
  • 一、TOKEN Token的特點: Token原理: TOKEN的實現: Token用在:登錄校驗。 TOKEN與session對比有更高的安全性,無狀態可擴展性和多平臺跨越的優點。 TOKEN可以完美解決跨站請求偽造,負載均衡,無狀態可擴展性等問題。 二、敏捷開發 敏捷開發是以人為核心,迭代循序漸 ...
  • redis資料庫 redis有以下三個特點 redis支持數據的持久化,可以將記憶體中的數據保存在磁碟中,重啟的時候可以再次載入進行使用 redis不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲 redis支持數據的備份,即master-s ...
  • 嚴謹轉載--否則追究法律責任 作者 王加鴻 bug 1 解決方案 將這兩個路徑下的文件清空即可 ↑ 解決方案: 暫無(如無法連接,會自動創建連接,但可能長久後會造成連接過多) bug 3 解決方案:初步分析,是埋點數據產生的json格式解析出問題了 後來查看源代碼在org.apache.flume. ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...