Oracle轉PostgreSQL之start with / connect by

Oracle分層查詢

Oracle中start with / connect by提供分層查詢的能力,從START WITH開始遍歷記錄,遞歸查詢結果集直到拿到所有滿足條件的結果。

例如下面測試數據:

drop table sr_menu;
create table sr_menu(  
  id number(10) not null, 
  parent number(10),
  title varchar2(50)
);
insert into sr_menu values (1, null, 'level 0');
insert into sr_menu values (2, 1, 'level 1');
insert into sr_menu values (3, 1, 'level 1');
insert into sr_menu values (4, 1, 'level 1');
insert into sr_menu values (5, 3, 'level 2');
commit;

select * from sr_menu;

        ID     PARENT TITLE                                             
---------- ---------- --------------------------------------------------
         1            level 0                                           
         2          1 level 1                                           
         3          1 level 1                                           
         5          3 level 2                                           
         4          1 level 1 

有這樣的邏輯

level 0:          1
            /   /   \   \
level 1:   2   3     4   5
              /
level 2:     5

業務上如果需要查詢跟節點1所以的子節點,在Oracle中可以使用以下語法:

select * from sr_menu 
start with id = 1 
connect by prior id = parent;
        ID     PARENT TITLE                                             
---------- ---------- --------------------------------------------------
         1            level 0                                           
         2          1 level 1                                           
         3          1 level 1                                           
         5          3 level 2                                           
         4          1 level 1  

查詢時會用上一層的id=1(prior修飾)和當前的parent比較,查詢出第二層符合條件的數據:

         2          1 level 1                                           
         3          1 level 1 
         4          1 level 1  

後面繼續遞歸,使用上一層的id=2/3/4去匹配下面的數據,從id=3中得到:

         5          3 level 2                                           

PostgreSQL分層查詢改造

Oracle 分層查詢其實是一種遞歸查詢的方式,用第一層查詢的結果遞歸出後一層。在 Postgresql 中可以使用 WITH RECURSIVE 語法實現相同的功能。

普通的 WITH 子句可以實現 CTE 的功能,加上 RECURSIVE 關鍵字可以進一步在 WITH 內引用自己的輸出實現遞歸,例如對於上面 SQL 的改寫,可以實現完全相同的業務邏輯:

WITH RECURSIVE a AS (
SELECT id, parent, title
  FROM sr_menu
  WHERE id = 1
UNION ALL
  SELECT d.id, d.parent, d.title
  FROM sr_menu d
  JOIN a ON a.id = d.parent )
SELECT id, parent, title FROM a;

 id | parent |  title
----+--------+---------
  1 |        | level 0
  2 |      1 | level 1
  3 |      1 | level 1
  4 |      1 | level 1
  5 |      3 | level 2

WITH 內使用 UNION ALL 的第一張對應 START WITH語句,一般是一個固定結果集的查詢條件。

UNION ALL的第二張表join ... a.id,表示連接當前 with 子句的查詢結果,這樣反覆遞歸直到所有數據查詢完畢。

從遞歸深度也可以看出執行過程:

WITH RECURSIVE a AS (
SELECT id, parent, title, 1::integer recursion_level
  FROM sr_menu
  WHERE id = 1
UNION ALL
  SELECT d.id, d.parent, d.title, a.recursion_level +1
  FROM sr_menu d
  JOIN a ON a.id = d.parent )
SELECT * FROM a;

 id | parent |  title  | recursion_level
----+--------+---------+-----------------
  1 |        | level 0 |               1
  2 |      1 | level 1 |               2
  3 |      1 | level 1 |               2
  4 |      1 | level 1 |               2
  5 |      3 | level 2 |               3

有關WITH RECURSIVE

WITH RECURSIVE t(n) AS (
    VALUES (1)
  UNION ALL
    SELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

遞歸WITH的執行流程:

  1. 計算非遞歸項(UNION ALL內的固定查詢部分,例如上面的VALUES(1))把結果放在臨時表A中
  2. 臨時表不爲空,重複下列步驟:
    1. 計算遞歸項(UNION ALL 內的遞歸部分),用臨時表A當作遞歸自引用表。查詢結果記錄到臨時表B
    2. 用B的數據庫覆蓋A,清空B
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章