1. 程式人生 > 其它 >理解 KingbaseES 中的遞迴查詢

理解 KingbaseES 中的遞迴查詢

關鍵字:SQL,CTE,遞迴查詢

概述:通常遞迴查詢是一個有難度的話題,儘管如此,它們仍使您能夠完成在 SQL 中無法實現的操作。本文通過示例進行了簡單介紹,並展示了與 PL/SQL的遞迴查詢實現的差異。

一、公用表表達式(WITH子句)

公用表表達式(CTE)可以被看作是一個檢視,只適用於一個單一的查詢:

WITH ctename AS (
   SELECT ...
)
SELECT ...
FROM ctename ...

這也可以寫成 中的子查詢FROM,但使用 CTE 有一些優點:

  • 查詢變得更具可讀性。
  • 您可以在查詢中多次引用 CTE,並且只會計算一次。
  • 您可以在 CTE 中使用資料修改語句(通常帶有RETURNING子句)。

請注意,在 V8R3 ,總是物化 CTE。這意味著,CTE 是獨立於包含查詢計算的。從 V8R6 開始,CTE 可以“內聯”到查詢中,這提供了進一步的優化潛力。

二、遞迴查詢的語法

遞迴查詢是使用遞迴 CTE編寫的,即包含RECURSIVE關鍵字的CTE :

WITH RECURSIVE ctename AS (
   SELECT /* non-recursive branch, cannot reference "ctename" */
   UNION [ALL]
   SELECT /* recursive branch referencing "ctename" */
)
SELECT ...
FROM ctename ...

三、如何處理遞迴查詢

KingbaseES內部使用 WorkTable 來處理遞迴 CTE。這種處理並不是真正的遞迴,而是迭代:

首先,通過執行 CTE 的非遞迴分支來初始化WorkTable。CTE 的結果也用這個結果集初始化。如果遞迴 CTE 使用UNION而不是UNION ALL,則刪除重複的行。

然後,KingbaseES重複以下操作,直到WorkTable為空:

  • 評估 CTE 的遞迴分支,用WorkTable替換對 CTE 的引用。
  • 將所有結果行新增到 CTE 結果。如果UNION用於合併分支,則丟棄重複的行。
  • 用上一步中的所有新行替換WorkTable(不包括任何已刪除的重複行)。

請注意,到目前為止,CTE的自引用分支並未使用完整的 CTE 結果執行,而是僅使用自上次迭代(WorkTable)以來的新行。

必須意識到這裡無限迴圈的危險:如果迭代永遠不會結束,查詢將一直執行直到結果表變得足夠大以導致錯誤。有兩種方法可以處理:

  • 通常,您可以通過使用 UNION來避免無限遞迴,這會刪除重複的結果行(但當然需要額外的處理工作)。
  • 另一種方法是LIMIT在使用 CTE 的查詢上放置一個子句,因為如果遞迴 CTE 計算的行數與父查詢獲取的行數一樣多,KingbaseES將停止處理。請注意,此技術不可移植到其他符合標準的資料庫。

請看實際執行計劃:

test=# explain WITH RECURSIVE ctename AS (
test(# SELECT empno, ename
test(# FROM emp
test(# WHERE empno = 7566
test(# UNION ALL
test(# SELECT emp.empno, emp.ename
test(# FROM emp JOIN ctename ON emp.mgr = ctename.empno
test(# )
test-# SELECT * FROM ctename;

--------------------------------------------------------------------------------------------------
 CTE Scan on ctename  (cost=417.62..489.74 rows=3606 width=36)
   CTE ctename
     ->  Recursive Union  (cost=0.00..417.62 rows=3606 width=36)
           ->  Seq Scan on emp  (cost=0.00..25.00 rows=6 width=36)
                 Filter: (empno = 7566)
           ->  Hash Join  (cost=1.95..32.05 rows=360 width=36)
                 Hash Cond: (emp_1.mgr = ctename_1.empno)
                 ->  Seq Scan on emp emp_1  (cost=0.00..22.00 rows=1200 width=40)
                 ->  Hash  (cost=1.20..1.20 rows=60 width=4)
                       ->  WorkTable Scan on ctename ctename_1  (cost=0.00..1.20 rows=60 width=4)

四、一個簡單的例子

讓我們假設一個像這樣的自引用表

TABLE emp;
 
 empno | ename  |    job    | mgr  |  hiredate  |   sal   |  comm   | deptno 
-------+--------+-----------+------+------------+---------+---------+--------
  7839 | KING   | PRESIDENT |      | 1981-11-17 | 5000.00 |         |     10
  7698 | BLAKE  | MANAGER   | 7839 | 1981-05-01 | 2850.00 |         |     30
  7782 | CLARK  | MANAGER   | 7839 | 1981-06-09 | 2450.00 |         |     10
  7566 | JONES  | MANAGER   | 7839 | 1981-04-02 | 2975.00 |         |     20
  7902 | FORD   | ANALYST   | 7566 | 1981-12-03 | 3000.00 |         |     20
  7369 | SMITH  | CLERK     | 7902 | 1980-12-17 |  800.00 |         |     20
  7499 | ALLEN  | SALESMAN  | 7698 | 1981-02-20 | 1600.00 |  300.00 |     30
  7521 | WARD   | SALESMAN  | 7698 | 1981-02-22 | 1250.00 |  500.00 |     30
  7654 | MARTIN | SALESMAN  | 7698 | 1981-09-28 | 1250.00 | 1400.00 |     30
  7844 | TURNER | SALESMAN  | 7698 | 1981-09-08 | 1500.00 |    0.00 |     30
  7900 | JAMES  | CLERK     | 7698 | 1981-12-03 |  950.00 |         |     30
  7934 | MILLER | CLERK     | 7782 | 1982-01-23 | 1300.00 |         |     10
(12 rows)

我們要查詢人員 7566 的所有下屬,包括人員本身。查詢的非遞迴分支將是:

SELECT empno, ename
FROM emp
WHERE empno = 7566;

遞迴分支會找到WorkTable中所有條目的所有下級:

SELECT emp.empno, emp.ename
FROM emp JOIN ctename ON emp.mgr = ctename.empno;

可以假設依賴項不包含迴圈(沒有人是他或她自己的經理,直接或間接)。所以可以將查詢與 UNION ALL 結合起來,因為不會發生重複。所以完整查詢將是:

WITH RECURSIVE ctename AS (
      SELECT empno, ename
      FROM emp
      WHERE empno = 7566
   UNION ALL
      SELECT emp.empno, emp.ename
      FROM emp JOIN ctename ON emp.mgr = ctename.empno
)
SELECT * FROM ctename;
 
 empno | ename 
-------+-------
  7566 | JONES
  7902 | FORD
  7369 | SMITH
(3 rows)

五、新增生成的列

有時您想新增更多資訊,例如層級。您可以通過將起始級別新增為非遞迴分支中的常量來實現。在遞迴分支中,您只需將 1 新增到級別:

WITH RECURSIVE ctename AS (
      SELECT empno, ename,
             0 AS level
      FROM emp
      WHERE empno = 7566
   UNION ALL
      SELECT emp.empno, emp.ename,
             ctename.level + 1
      FROM emp
         JOIN ctename ON emp.mgr = ctename.empno
)
SELECT * FROM ctename;
 
 empno | ename | level
-------+-------+-------
  7566 | JONES |     0
  7902 | FORD  |     1
  7369 | SMITH |     2
(3 rows)

如果UNION在迴圈引用的情況下使用避免重複行,則不能使用此技術。這是因為新增level會使之前相同的行不同。但在那種情況下,分層級別無論如何都沒有多大意義,因為一個條目可能出現在無限多個級別上。

另一個常見的要求是收集“路徑”中的所有祖先:

WITH RECURSIVE ctename AS (
      SELECT empno, ename,
             ename AS path
      FROM emp
      WHERE empno = 7566
   UNION ALL
      SELECT emp.empno, emp.ename,
             ctename.path || ' -> ' || emp.ename
      FROM emp
         JOIN ctename ON emp.mgr = ctename.empno
)
SELECT * FROM ctename;
 
 empno | ename |          path          
-------+-------+------------------------
  7566 | JONES | JONES
  7902 | FORD  | JONES -> FORD
  7369 | SMITH | JONES -> FORD -> SMITH

六、與 PLSQL 的比較

PLSQL對於不符合 SQL 標準的遞迴查詢有不同的語法。原始示例如下所示:

SELECT empno, ename
FROM emp
START WITH empno = 7566
CONNECT BY PRIOR empno = mgr;
 
     EMPNO ENAME
---------- ----------
      7566 JONES
      7902 FORD
      7369 SMITH

這種語法更簡潔,但不如遞迴 CTE 強大。對於涉及連線的更復雜的查詢,它可能變得困難和混亂。將 PLSQL “分層查詢”轉換為遞迴 CTE 總是很容易的:

  • 非遞迴分支是不帶CONNECT BY子句但包含START WITH子句的 Oracle 查詢。
  • 遞迴分支是不帶START WITH子句但包含CONNECT BY子句的 Oracle 查詢。新增具有遞迴 CTE 名稱的PRIOR聯接,並用來自該聯接 CTE 的列替換所有列。
  • 如果 Oracle 查詢使用CONNECT BY NOCYCLE,則使用UNION,否則使用UNION ALL。

一般把connect by語法稱為遞迴查詢,然而嚴格來說這是一個錯誤的叫法。因為它無法把當前層所計算得到的值傳遞到下一層,所以對它的稱呼都是Hierarchical Queries in Oracle (CONNECT BY) 。

七、遞迴查詢的真正實力

如果沒有遞迴 CTE,很多可以用過程語言編寫的東西就不能用 SQL 編寫。這通常影響資料庫的使用,因為 SQL 是用來查詢資料庫的。但是遞迴 CTE 使 SQL過程程式碼更完善,也就是說,它可以執行與任何其他程式語言相同的計算。前面的示例表明遞迴 CTE 可以完成您在 SQL 中無法執行的有用工作。

作為遞迴查詢功能的示例,這裡是一個遞迴 CTE,它計算斐波那契數列的第一個元素:

WITH RECURSIVE t(n,last_n,cnt) AS (
  SELECT 1,0,1 FROM DUAL
  UNION ALL
  SELECT t.n+t.last_n, t.n, t.cnt+1 
    FROM t   
  )
SELECT * FROM T limit 10

 n  | last_n | cnt 
----+--------+-----
  1 |      0 |   1
  1 |      1 |   2
  2 |      1 |   3
  3 |      2 |   4
  5 |      3 |   5
  8 |      5 |   6
 13 |      8 |   7
 21 |     13 |   8
 34 |     21 |   9
 55 |     34 |  10

  

KINGBASE研究院