迁移 Oracle 到 PostgreSQL: 层次查询 CONNECT BY

五月 19, 2023

在 Oracle 中,层次查询 START WITH ... CONNECT BY 可以用于创建从给定的行开始的单向链表结构。链表可以采用树的形式,并且没有平衡要求。

Oracle 示例

为了说明这一点,让我们从一个查询开始,并假设表中有 5 行。

SELECT * FROM person;
 last_name  | first_name | id | parent_id
------------+------------+----+-----------
 Goodman    | Jordan     |  1 |    (null)
 George     | Callie     |  2 |         1
 Steele     | Donald     |  3 |         1
 Allison    | Richard    |  4 |         1
 Thomas     | Shaun      |  5 |         3
(5 rows)

下面是使用 Oracle 语法的表的分层查询。

select id, parent_id from person
  start with parent_id IS NULL
  connect by prior id = parent_id;
 id | parent_id
----+-----------
  1 |    (null)
  4 |         1
  3 |         1
  2 |         1
  5 |         3

PostgreSQL 示例

我们可以使用 PostgreSQL 的 WITH RECURSIVE 查询,实现和 Oracle CONNECT BY 类似的功能。

WITH RECURSIVE a AS (
    SELECT id, parent_id FROM person
      WHERE parent_id IS NULL
  UNION ALL
    SELECT d.id, d.parent_id
      FROM person d
      JOIN a ON a.id = d.parent_id )
SELECT id, parent_id FROM a;
 id | parent_id
----+-----------
  1 |    (null)
  4 |         1
  3 |         1
  2 |         1
  5 |         3
(5 rows)

这是一个“公用表表达式”(CTE)。它定义了一组查询,这些查询将在同一语句中执行,而不仅仅是在同一事务中执行。您可以有任意数量的括号表达式和最后的语句。对于这种用法,我们只需要一个。通过将声明该语句为递归,它将迭代执行,直到不再返回行。

SELECT
UNION ALL
SELECT

这是递归查询的规定短语。它在文档中被定义为区分起点和递归算法的方法。在 Oracle 术语中,您可以将它们视为与 CONNECT BY 子句联合的 START WITH 子句。

JOIN a ON a.id = d.parent_id

这是 CTE 语句的自联接,该语句将上一行数据提供给后续迭代。

为了说明其工作原理,让我们向查询添加一个迭代指示器:

WITH RECURSIVE a AS (
    SELECT id, parent_id, 1::integer recursion_level
      FROM person
      WHERE parent_id IS NULL
  UNION ALL
    SELECT d.id, d.parent_id, a.recursion_level +1
      FROM person d
      JOIN a ON a.id = d.parent_id )
SELECT * FROM a;
 id | parent_id | recursion_level
----+-----------+-----------------
  1 |    (null) |               1
  4 |         1 |               2
  3 |         1 |               2
  2 |         1 |               2
  5 |         3 |               3
(5 rows)

我们用一个值初始化递归级别指标。请注意,在返回的行中,第一个递归级别仅出现一次。这是因为第一个子句只执行一次。

第二个子句是迭代逻辑生效的地方。在这里,我们可以查看上一行数据以及当前行数据。这允许我们执行递归计算。