WITH 子句递归

包含 RECURSIVE 选项的 WITH 子句可以重复执行 UNION 或 UNION ALL 查询,从而迭代其自身的输出。递归查询在处理分层结构(例如,经理下属关系)或树状结构数据(如分类法)等自引用数据时十分有用。

配置参数 WithClauseRecursionLimit(默认设置为 8)将设置递归的最大深度。您可以分别使用 ALTER DATABASE 和 ALTER SESSION 在数据库和会话范围内设置此参数。递归将会继续,直到达到配置的最大深度为止,或者直到最后一次迭代返回没有数据为止。

可以按如下方式指定递归 WITH 子句:

WITH [ /*+ENABLE_WITH_CLAUSE_MATERIALIZATION*/ ] RECURSIVE
   cte‑identifier [ ( column-aliases ) ] AS (
     non-recursive-term
     UNION [ ALL ]
     recursive-term
   )

非递归项和递归项由 UNION 或 UNION ALL 分隔:

  • non-recursive-term 查询将其结果集设置在 cte-identifier,在 recursive-term 中递归。

  • UNION 语句的 recursive-term 以递归方式迭代其自身输出。当递归完成时,所有迭代的结果均会编译并在 cte-identifier 中设置。

例如:


=> ALTER SESSION SET PARAMETER WithClauseRecursionLimit=4; -- maximum recursion depth = 4
=> WITH RECURSIVE nums (n) AS (
   SELECT 1 -- non-recursive (base) term
   UNION ALL
     SELECT n+1 FROM nums -- recursive term
  )
SELECT n FROM nums; -- primary query

此简单查询按如下方式执行:

  1. 执行 WITH RECURSIVE 子句:

    • 评估非递归项 SELECT 1,并将结果集 (1) 放入 nums

    • 迭代 UNION ALL 查询 (SELECT n+1),直到迭代次数大于配置参数 WithClauseRecursionLimit。

    • 合并所有 UNION 查询的结果,并将结果集设置在 nums 中,然后退出到主要查询。

  2. 执行主要查询 SELECT n FROM nums

    
     n
    ---
     1
     2
     3
     4
     5
    (5 rows)
    

在本例中,根据 WithClauseRecursionLimit,WITH RECURSIVE 子句在四次迭代后退出。如果将 WithClauseRecursionLimit 还原为其默认值 8,则子句在八次迭代后退出:


=> ALTER SESSION CLEAR PARAMETER WithClauseRecursionLimit;
=> WITH RECURSIVE nums (n) AS (
   SELECT 1
   UNION ALL
     SELECT n+1 FROM nums
  )
SELECT n FROM nums;
 n
---
 1
 2
 3
 4
 5
 6
 7
 8
 9
(9 rows)

限制

存在以下限制:

  • 非递归项的 SELECT 列表不能包含通配符 *(星号)或函数 MATCH_COLUMNS

  • 递归项只能引用目标 CTE 一次。

  • 递归引用不能出现在外联接中。

  • 递归引用不能出现在子查询中。

  • WITH 子句不支持 UNION 选项 ORDER BY、LIMIT 和 OFFSET。

示例

一家小型软件公司维护以下有关员工及其经理的数据:

=> SELECT * FROM personnel.employees ORDER BY emp_id;
 emp_id |   fname   |   lname   | section_id |    section_name     |  section_leader  | leader_id
--------+-----------+-----------+------------+---------------------+------------------+-----------
      0 | Stephen   | Mulligan  |          0 |                     |                  |
      1 | Michael   | North     |        201 | Development         | Zoe Black        |         3
      2 | Megan     | Berry     |        202 | QA                  | Richard Chan     |        18
      3 | Zoe       | Black     |        101 | Product Development | Renuka Patil     |        24
      4 | Tim       | James     |        203 | IT                  | Ebuka Udechukwu  |        17
      5 | Bella     | Tucker    |        201 | Development         | Zoe Black        |         3
      6 | Alexandra | Climo     |        202 | QA                  | Richard Chan     |        18
      7 | Leonard   | Gray      |        203 | IT                  | Ebuka Udechukwu  |        17
      8 | Carolyn   | Henderson |        201 | Development         | Zoe Black        |         3
      9 | Ryan      | Henderson |        201 | Development         | Zoe Black        |         3
     10 | Frank     | Tucker    |        205 | Sales               | Benjamin Glover  |        29
     11 | Nathan    | Ferguson  |        102 | Sales Marketing     | Eric Redfield    |        28
     12 | Kevin     | Rampling  |        101 | Product Development | Renuka Patil     |        24
     13 | Tuy Kim   | Duong     |        201 | Development         | Zoe Black        |         3
     14 | Dwipendra | Sing      |        204 | Tech Support        | Sarah Feldman    |        26
     15 | Dylan     | Wijman    |        206 | Documentation       | Kevin Rampling   |        12
     16 | Tamar     | Sasson    |        207 | Marketing           | Nathan Ferguson  |        11
     17 | Ebuka     | Udechukwu |        101 | Product Development | Renuka Patil     |        24
     18 | Richard   | Chan      |        101 | Product Development | Renuka Patil     |        24
     19 | Maria     | del Rio   |        201 | Development         | Zoe Black        |         3
     20 | Hua       | Song      |        204 | Tech Support        | Sarah Feldman    |        26
     21 | Carmen    | Lopez     |        204 | Tech Support        | Sarah Feldman    |        26
     22 | Edgar     | Mejia     |        206 | Documentation       | Kevin Rampling   |        12
     23 | Riad      | Salim     |        201 | Development         | Zoe Black        |         3
     24 | Renuka    | Patil     |        100 | Executive Office    | Stephen Mulligan |         0
     25 | Rina      | Dsouza    |        202 | QA                  | Richard Chan     |        18
     26 | Sarah     | Feldman   |        101 | Product Development | Renuka Patil     |        24
     27 | Max       | Mills     |        102 | Sales Marketing     | Eric Redfield    |        28
     28 | Eric      | Redfield  |        100 | Executive Office    | Stephen Mulligan |         0
     29 | Benjamin  | Glover    |        102 | Sales Marketing     | Eric Redfield    |        28
     30 | Dominic   | King      |        205 | Sales               | Benjamin Glover  |        29
     32 | Ryan      | Metcalfe  |        206 | Documentation       | Kevin Rampling   |        12
     33 | Piers     | Paige     |        201 | Development         | Zoe Black        |         3
     34 | Nicola    | Kelly     |        207 | Marketing           | Nathan Ferguson  |        11
(34 rows)

您可以通过 WITH RECURSIVE 查询此数据以了解员工与经理的关系。例如,以下查询的 WITH RECURSIVE 子句获取员工 Eric Redfield(包括他的所有直接下属和间接下属员工)的员工-经理关系:

WITH RECURSIVE managers (employeeID, employeeName, sectionID, section, lead, leadID)
 AS (SELECT emp_id, fname||' '||lname, section_id, section_name, section_leader, leader_id
      FROM personnel.employees WHERE fname||' '||lname = 'Eric Redfield'
 UNION
    SELECT emp_id, fname||' '||lname AS employee_name, section_id, section_name, section_leader, leader_id FROM personnel.employees e
      JOIN managers m ON m.employeeID = e.leader_id)
 SELECT employeeID, employeeName, lead AS 'Reports to', section, leadID from managers ORDER BY sectionID, employeeName;

WITH RECURSIVE 子句定义了 CTE managers,然后分两个阶段执行:

  1. 非递归项使用从 personnel.employees 查询的数据填充 managers

  2. 递归项的 UNION 查询迭代它自己的输出,直到在第四个循环中找不到更多数据。然后,所有迭代结果进行编译并设置在 managers 中,WITH CLAUSE 退出到主要查询。

主要查询从 managers 返回三个级别的数据(每个递归迭代一个级别):

类似地,以下查询遍历相同的数据以获取员工 Richard Chan(他在公司管理层中比 Eric Redfield 低一级)的所有员工-经理关系:

WITH RECURSIVE managers (employeeID, employeeName, sectionID, section, lead, leadID)
 AS (SELECT emp_id, fname||' '||lname, section_id, section_name, section_leader, leader_id
      FROM personnel.employees WHERE fname||' '||lname = 'Richard Chan'
 UNION
    SELECT emp_id, fname||' '||lname AS employee_name, section_id, section_name, section_leader, leader_id FROM personnel.employees e
      JOIN managers m ON m.employeeID = e.leader_id)
 SELECT employeeID, employeeName, lead AS 'Reports to', section, leadID from managers ORDER BY sectionID, employeeName;

WITH RECURSIVE 子句像以前一样执行,但这次它在两次迭代后找不到更多数据而退出。因此,主要查询从 managers 返回两个级别的数据:

WITH RECURSIVE 实体化

默认情况下,实体化处于禁用状态。在这种情况下,Vertica 将 WITH RECURSIVE 查询重写为子查询,其数量与所需的递归级别一样多。

如果递归非常深,则大量查询重写可能会产生相当大的开销,从而对性能产生不利影响并耗尽系统资源。在这种情况下,请考虑使用配置参数 WithClauseMaterialization 或提示 ENABLE_WITH_CLAUSE_MATERIALIZATION 来启用实体化。在任何一种情况下,来自所有递归级别的中间结果集都将写入本地临时表。当递归完成时,所有临时表中的中间结果都进行编译并传递给主要查询。