簡化 SQL 遞迴查詢
背景描述
自引用型別的表結構處理起來比較麻煩,比如“分類”表,通常包括自己的ID和父分類ID,當我們要做 父分類路徑、子分類路徑 之類的查詢時很不方便,例如我們會使用巢狀查詢,或者新增冗餘欄位來記錄分類路徑資訊,都比較麻煩,有沒有簡單的辦法呢?
解決方法就是使用 CTE (Common Table Expression),通用表表達式。
下面我們先認識一下 CTE ,然後通過幾個實際查詢示例來深入理解,最後會提供測試資料,以方便自己動手實踐(在mysql8和postgres10上都測試過)。
什麼是 CTE?
各大主流資料庫都支援 CTE ,mysql8 中也支援了。
簡單理解,CTE 就是一個有名字的結果集,就像一個普通表一樣,可以被用在 select 語句中。
CTE 有 迴圈 和 非迴圈 形式,非迴圈形式比較簡單,就像一個命了名的子查詢,例如:
這裡定義了2個CTE:
-
one,有一個列 number one_,值為 1。
-
two,有一個列 number_two,值為 2。
執行結果:
迴圈形式 的複雜一點,先看一個示例:
迴圈式CTE的執行思路:
第一個 select 會產生N個種子記錄,新增到結果集,然後執行後面的 select,這個 select 會基於前面 select 產生的結果集執行,把執行結果新增到結果集,接下來會繼續執行這個 select,還是基於上一個 select 產生的資料,並把執行結果新增到結果集,一直到執行結果為空,結束。2個 select 的聯結詞包括 UIO/">NION ALL 和 UNION ,區別就是 UNION 會把重複的結果刪掉。
結合上面的例子:
-
第一個 select 產生一條資料,列名為 "n",值為 “1”,放入結果集。
-
第二個 select 在這條資料的基礎上執行,符合 n<10 這個條件,執行 n+1,產生一條結果資料 “2”,放入結果集。
-
第二個 select 繼續執行,基於上次執行結果 “2” 執行,符合 n<10 這個條件,執行 n+1,產生一條結果資料 “3”,放入結果集。
-
...
-
一直執行到不符合 n<10 這個條件,執行結果集為空,結束。
示例
示例用的的資料:
表 categories
層級結構是這樣的:
(1)示例1
查詢 “Child A1” 這個分類及其子分類,並顯示層級深度。
執行結果:
分析:
-
第一個select得到結果資料 3 , Child A1 , 1 。
-
第二個select把 categories 表和第一個select的結果集進行關聯,得到2條資料, 7 , Grandchild A1a 和 8 , Grandchild A1b ,這2條資料都會在 3 , Child A1 , 1 基礎上計算 relative_depth + 1 ,所以結果都為 "2"。
-
第二個 select 繼續執行,發現結果集為空了,停止。
(2)示例2
查詢 "Grandchild A1b" 的所有父分類。
執行結果:
執行思路與示例1相同。
(3)示例3
查詢根分類及其所有子分類。
執行結果:
分析:
-
第一個 select 得到2條記錄 Root A 和 Root B 。
-
第二個 select 把 categories 表和第一個 select 的結果集進行聯合,找到了 Root A 的2個子分類 Child A1 和 Child A2 ,還有 Root B 的2個子分類 Child B1 和 Child B2 。
-
第二個 select 繼續執行,把 categories 和上次執行結果聯合,基於 Child A1 、 Child A2 、 Child B1 、 Child B2 查詢,找到了 Grandchild A1a 和 Grandchild A1b 。
-
第二個 select 繼續執行,發現結果集為空了,停止。
測試環境準備
建表:
插入測試資料: