巧用 DB2 遞歸 SQL

開始之前

遞歸 SQL 是在關係數據庫中解析層次結構數據的非常有效手段。它可以用於高效地查詢組織架構、零件表單、定單系統、網絡結構等層次型數據。雖然遞歸 SQL 的語法較一般 SQL 要複雜一些,但只要理解了其基本原理和幾個基本組成部分,程序員也不難寫出巧妙的遞歸 SQL 來代替繁複冗長的應用程序代碼。

本文將介紹遞歸 SQL 的語法,工作原理及其在層次型數據查詢方面的應用實例,以幫助數據庫程序員簡化程序,提高效率。除了查詢層次結構數據,遞歸 SQL 還可以應用在其他方面,本文也將介紹其在構造測試數據方面的巧妙應用。

DB2 遞歸 SQL 的語法

遞歸 SQL 在 DB2 中通過公共表表達式 (CTE,Common Table Expression) 來實現。遞歸 SQL 由遞歸 CTE 以及對遞歸 CTE 結果的查詢組成。那什麼是遞歸 CTE 呢?簡言之,如果 CTE 中的 FULLSELECT 在 FROM 子句中引用到 CTE 本身,就是遞歸 CTE。遞歸 CTE 包含以下三個組成部分:

  • 初始查詢

    初始查詢是 CTE 中對基本表進行查詢的部分。CTE 定義中的第一個 FULLSELECT 必須不包含對 CTE 自身的應用,即必須是初始查詢。

  • 遞歸查詢

    遞歸查詢就是通過對 CTE 自身的引用,從而啓動遞歸邏輯的查詢。遞歸查詢需要遵循以下幾個規則 :

    1. 遞歸查詢和初始查詢結果必須包含相同數量的數據列;
    2. 遞歸查詢和初始查詢結果數據列的、長度等必須一致;
    3. 遞歸查詢不能包含 GROUP BY 或者 HAVING 子句;
    4. 遞歸查詢不能包含 Outer Join;
    5. 遞歸查詢不能包含子查詢 (Subquery);
    6. 遞歸查詢必須用 UNION ALL 聯結。
  • 終止條件

    終止條件通常是隱性的,即如果前一次遞歸查詢返回的結果集爲空,則終止遞歸;但是也可以在遞歸查詢中設定終止條件,如限定遞歸查詢的深度等。

下面我們用一個簡單的例子來說明初始查詢,遞歸查詢和終止條件是如何實現一個遞歸 CTE 的。

工作原理

以下通過一個描述節點層次關係的實例來說明遞歸 SQL 的工作原理。

首先執行清單 1 中的 SQL 語句來建立該實例所用的表和數據。

清單 1. 創建 NODE 表和數據
1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE NODE(
 CHILD INTEGER NOT NULL,
 PARENT INTEGER NOT NULL);
 
INSERT INTO NODE VALUES(1, 0);
INSERT INTO NODE VALUES(2, 6);
INSERT INTO NODE VALUES(3, 1);
INSERT INTO NODE VALUES(4, 5);
INSERT INTO NODE VALUES(5, 3);
INSERT INTO NODE VALUES(6, 3);
INSERT INTO NODE VALUES(7, 5);
INSERT INTO NODE VALUES(8, 5);

成功執行清單 1 中的 SQL 後,NODE 表的內容如表 1 所示。

表 1. NODE 表

則清單 2 中的 SQL 將得出 NODE 表的層次結構。

清單 2. NODE 表層次結構查詢
1
2
3
4
5
6
7
8
9
10
11
12
WITH report(parent, child)
AS
(
SELECT parent, child
FROM node
WHERE parent = 0
 UNION ALL
SELECT b.parent, b.child
FROM report a, node b
WHERE b.parent = a.child
)
SELECT * FROM report;
圖 1. NODE 表層次結構查詢遞歸 SQL 的執行路徑圖
圖 1. NODE 表層次結構查詢遞歸 SQL 的執行路徑圖

圖 1 所示爲清單 2 中查詢的執行路徑圖。QB3 爲初始查詢,QB4 爲遞歸查詢。

運行步驟:

1 . 初始查詢返回初始結果集,這個查詢返回的就是頭節點,如表 2 所示。

表 2. 步驟 1 結果

2 . 遞歸查詢使用初始結果集作爲 report CTE 的內容通過 node.parent = report.child 連接 NODE 表得到下一個結果集,也就是頭節點 1 的子節點,如表 3 所示。

表 3. 步驟 2 結果

3 . 遞歸查詢迭代使用第 2 步的結果集作爲 report CTE 的輸入,繼續連接 NODE 表得到節點 3 的子節點,如表 4 所示。

表 4. 步驟 3 結果

4 . 使用第 3 步的結果集繼續迭代,取得下一個結果集,如表 5 所示。

表 5. 步驟 4 結果

5 . 使用第 4 步的結果集連接 NODE 表,返回爲空,遞歸查詢終止。最終返回結果爲以上所有步驟中得到的結果集的 UNION,如表 6 所示。

表 6. 步驟 5 結果

這樣就可以清楚的得到圖 2 所示的層次結構。

圖 2. NODE 表節點層次結構
圖 2. NODE 表節點層次結構

理解了遞歸 CTE 的工作原理 , 我們再用一個更爲實際的例子來展示遞歸 CTE 在有層次關係的數據庫表中的各種靈活應用, 看看以往需要通過多次查詢和大量應用程序代碼才能實現的功能 , 是如何通過一個簡單的遞歸 CTE 完成的。

層次型數據遞歸查詢應用

對於層次型的數據,使用遞歸 SQL 查詢十分方便,以下示例將基於如圖 3 所示的組織架構圖。

圖 3. 人員組織結構管理層次結構示例
圖 3. 人員組織結構管理層次結構示例

首先執行清單 3 中的 SQL 語句來建立表和數據。

清單 3. 創建 ORG 表和數據
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE ORG(
 EMPID INTEGER NOT NULL,
 EMPNAME VARCHAR(128) NOT NULL,
 MGRID INTEGER NOT NULL);
 
INSERT INTO ORG VALUES(1, 'Jack', 0);
INSERT INTO ORG VALUES(2, 'Mary', 1);
INSERT INTO ORG VALUES(3, 'Tom', 1);
INSERT INTO ORG VALUES(4, 'Ben', 2);
INSERT INTO ORG VALUES(5, 'John', 3);
INSERT INTO ORG VALUES(6, 'Emily', 3);
INSERT INTO ORG VALUES(7, 'Kate', 3);
INSERT INTO ORG VALUES(8, 'Mark', 6);

此時,ORG 表內容如表 7 所示。

表 7. ORG 表

1. 從上往下的查詢,列出 Tom 管理的所有員工的名字。對應的遞歸 SQL 如清單 4 所示。

清單 4. 查詢 Tom 管理的所有員工
1
2
3
4
5
6
7
8
9
10
11
12
13
WITH report(empid,empname)
AS
(
SELECT empid, empname
FROM org
WHERE mgrid = 3
UNION ALL
 SELECT a.empid, a.empname
FROM org a, report b
WHERE a.mgrid= b.empid
)
SELECT empname
FROM report;

執行結果如表 8、圖 4 所示。

表 8. Tom 管理的所有員工
圖 4. Tom 管理的所有員工
圖 4. Tom 管理的所有員工

2. 從下往上的查詢,列出 Mark 的報告鏈。對應的遞歸 SQL 如清單 5 所示。

清單 5. 查詢 Mark 的報告鏈
1
2
3
4
5
6
7
8
9
10
11
12
13
WITH report(empid,empname,mgrid)
AS
(
SELECT empid, empname,mgrid
FROM org
WHERE empid = 8
 UNION ALL
SELECT a.empid, a.empname, a.mgrid
FROM org a, report b
WHERE a.empid= b.mgrid
)
SELECT empname
FROM report;

執行結果如表 9、圖 5 所示。

表 9. Mark 的報告鏈
圖 5. Mark 的報告鏈
圖 5. Mark 的報告鏈

3. 使用 level 列控制遞歸深度。遞歸 SQL 可能造成循環,在 CTE 定義中設置一個 level 列來控制深度,使遞歸提前終止是常用的避免循環的做法。同時 level 列還可以表明層次結構中的層數。比如修改本例的 SQL,加入 level 列,我們可以看到 Jack 共管理了幾級人員,對應的遞歸 SQL 如清單 6 所示。

清單 6. 查詢 Jack 管理的層數
1
2
3
4
5
6
7
8
9
10
11
12
13
WITH report(level, empid, empname)
AS
(
SELECT 0, empid, empname
FROM org
WHERE empname = 'Jack'
UNION ALL
SELECT level+1, a.empid, a.empname
FROM org a, report b
WHERE a.mgrid= b.empid
)
SELECT max(level) AS MAX_LEVEL
FROM report;

執行結果如表 10 所示。

表 10. Jack 管理的層數

或者我們可以修改 SQL,查詢 Mark 的上級以及上上級經理是誰,對應的遞歸 SQL 如清單 7 所示。

清單 7. 查詢 Mark 的上層經理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
WITH report(level, empid, empname, mgrid)
AS
(
SELECT 0, empid, empname,mgrid
FROM org
WHERE empname = 'Mark'
 UNION ALL
SELECT level+1, a.empid, a.empname, a.mgrid
FROM org a, report b
WHERE a.empid= b.mgrid
)
SELECT level, empname
FROM report
WHERE level > 0;

執行結果如表 11 所示。

表 11. Mark 的上層經理

4. 彙總。彙總是計算總數,如果我們需要計算 Jack 管理的人員的總數,那隻要把 SQL 修改成如清單 8 所示就可以了。

清單 8. 查詢 Jack 管理的人數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
WITH report(level, empid, empname)
AS
(
SELECT 0, empid, empname
FROM org
WHERE empname = 'Jack'
 UNION ALL
SELECT level+1, a.empid, a.empname
FROM org a, report b
WHERE org.mgrid= report.empid
)
SELECT COUNT(*) AS TOTAL_MANAGED
FROM report
WHERE level > 0;

執行結果如表 12 所示。

表 12. Jack 管理的人數

但是我們如果要計算 ORG 表中所有人管理的人員總數,我們就需要從下往上進行彙總,對應的遞歸 SQL 如清單 9 所示。

清單 9. 查詢所有人管理的人數
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
WITH report(empid, empname, mgrid)
AS
(
-- 選擇 org 表中所有的行,即所有的員工
SELECT empid, empname, mgrid
FROM org
UNION ALL
-- 對應前一次結果集的每一行,在新的結果集中爲其經理插入一行
SELECT a.empid, a.empname, a.mgrid
FROM org a, report b
WHERE b.mgrid= a.empid
)
-- 因爲初始查詢中每個員工都有初始行,所以最後結果要減去 1
SELECT empid, empname, COUNT(*)-1 AS TOTAL_MANAGED
FROM report
GROUP BY empid,empname;

執行結果如表 13 所示。

表 13. 所有人管理的人數

運用遞歸 SQL 構造測試數據

遞歸 SQL 還有一個特性,就是它可以從一行數據遞歸產生多行數據。利用這個特性,遞歸 SQL 還可以用來構造特定類型的測試數據。

構造連續數據

假定需要構造一張時間表,包含一天的所有分鐘,即表 DAY 有一列爲 d_minute , 它的內容應該爲:00:00:00 00:01:00 00:02:00 … … 23:58:00 23:59:00

對應的遞歸 SQL 如清單 10 所示。

清單 10. 構造連續數據
1
2
3
4
5
6
7
8
9
10
INSERT INTO DAY
WITH temp(d_minute) AS
(
SELECT TIME('00:00:00') FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT d_minute + 1 MINUTE
FROM temp
WHERE d_minute < TIME('23:59:00')
)
SELECT * FROM temp;

初始查詢建立初始結果集,即從“00:00:00”開始,遞歸查詢迭代給結果集裏的數據一次加上 1 分鐘,最終得到一整天的分鐘數據。

構造階乘數列

利用遞歸 SQL 可以非常簡便地構造出階乘數列。清單 11 利用遞歸 SQL 得出 1 到 10 的階乘。

清單 11. 構造階乘數列
1
2
3
4
5
6
7
8
9
10
WITH temp(LEVEL, RESULT) AS
(
SELECT 1,1
FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT LEVEL+1,(LEVEL+1)*RESULT
FROM temp
WHERE LEVEL < 10
)
SELECT * FROM temp;

執行結果如表 14 所示。

表 14.1 到 10 的階乘數列

在這裏一定要注意在遞歸查詢中加入終止條件,不然 SQL 將無法退出迭代。

構造分區數據

假定 TEST 表是分區表,它以 key(integer) 列的值分爲 10 個區,這 10 個區分別爲 0~10000,10001~20000,20001 …… , 90000~10000。現在需要往 TEST 表裏插入數據,並且希望每個分區都能插入 1000 行數據。可使用清單 12 中的遞歸 SQL 實現。

清單 12. 構造分區數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
INSERT INTO test
WITH TEMP1 (NUM1) AS
(
SELECT 0
FROM SYSIBM.SYSDUMMY1
UNION ALL
SELECT NUM1 + 1
FROM TEMP1
WHERE NUM1 < 9
),
TEMP2 (NUM1, NUM2) AS
(
SELECT NUM1, 1
FROM TEMP1
UNION ALL
SELECT NUM1, NUM2 + 1
FROM TEMP2
WHERE NUM2 < 1000
),
TEMP3 AS
(
SELECT (NUM1 * 10000) + NUM2 AS NUM
FROM TEMP2
)
SELECT NUM AS KEY FROM TEMP3;

首先 CTE TEMP1 產生 0~9 對應 10 個分區;TEMP2 對應每個 TEMP1 的值產生 1~1000 共 1000 行數據;TEMP3 把 NUM1 放大再把兩者相加,得到符合條件的數據。在 CTE TEMP2 中還可以使用 RAND( ) 函數,產生對應各分區的隨機數據。

結束語

閱讀完本文,讀者應該能夠:

  1. 理解 DB2 中遞歸 SQL 的語法和工作原理;
  2. 利用遞歸 SQL 查詢具有層次關係的數據;
  3. 利用遞歸 SQL 構造具有同樣屬性的測試數據。

DB2 遞歸 SQL 爲處理層次型數據提供了非常有效的解決方法。通過使用 DB2 遞歸 SQL,對於特定類型的問題,我們可以簡化應用程序,極大地提高程序運行效率。本文還討論了遞歸 SQL 在構造測試數據方面的應用,希望能啓發讀者將這項技術靈活運用到更多新的領域。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章