12

我在 postgres 中的表如下所示,表存储了 ID 之间的链式关系,我想要一个可以产生类似“vc1”->“rc7”或“vc3”->“rc7”的结果的查询,我会仅查询第一列 ID1 中的 ID

ID1     ID2
"vc1"   "vc2"
"vc2"   "vc3"
"vc3"   "vc4"
"vc4"   "rc7"

所以我想在这里提供一些“head” id,我必须为其获取tail(链中的最后一个)id。

4

2 回答 2

38

这是一个简单的递归公用表表达式 ( WITH RECURSIVE) 的经典用法,在 PostgreSQL 8.4 及更高版本中可用。

在这里演示:http ://sqlfiddle.com/#!12/78e15/9

给定示例数据为 SQL:

CREATE TABLE Table1
    ("ID1" text, "ID2" text)
;

INSERT INTO Table1
    ("ID1", "ID2")
VALUES
    ('vc1', 'vc2'),
    ('vc2', 'vc3'),
    ('vc3', 'vc4'),
    ('vc4', 'rc7')
;

你可以写:

WITH RECURSIVE chain(from_id, to_id) AS (
  SELECT NULL, 'vc2'
  UNION
  SELECT c.to_id, t."ID2"
  FROM chain c
  LEFT OUTER JOIN Table1 t ON (t."ID1" = to_id)
  WHERE c.to_id IS NOT NULL
)
SELECT from_id FROM chain WHERE to_id IS NULL;

这样做是迭代地遍历链,将每一行chain作为从指针和到指针添加到表中。当它遇到不存在“to”引用的行时,它将为该行添加一个空的“to”引用。下一次迭代将注意到“to”引用为空并产生零行,这将导致迭代结束。

然后,外部查询通过具有不存在的 to_id 来选择已确定为链末端的行。

了解递归 CTE 需要付出一些努力。他们需要理解的关键是:

  • 它们从初始查询的输出开始,它们反复与“递归部分”(UNIONor之后的查询UNION ALL)的输出联合,直到递归部分不添加任何行。这会停止迭代。

  • 它们并不是真正的递归,更具迭代性,尽管它们适用于您可能使用递归的各种事情。

所以你基本上是在循环中构建一个表。您不能删除或更改行,只能添加新行,因此您通常需要一个外部查询来过滤结果以获取您想要的结果行。您通常会添加额外的列,其中包含用于跟踪迭代状态、控制停止条件等的中间数据。

它可以帮助查看未过滤的结果。如果我用一个简单的替换最终的汇总查询,SELECT * FROM chain我可以看到已生成的表:

 from_id | to_id 
---------+-------
         | vc2
 vc2     | vc3
 vc3     | vc4
 vc4     | rc7
 rc7     | 
(5 rows)

第一行是手动添加的起点行,您可以在其中指定要查找的内容 - 在本例中为vc2. 随后的每一行都由UNIONed 递归项添加,它对LEFT OUTER JOIN前一个结果执行 a 并返回一组新的行,这些行将前一个to_id(现在在from_id列中)与下一个配对to_id。如果LEFT OUTER JOIN不匹配,则to_id将为空,导致下一次调用现在返回行并结束迭代。

因为这个查询每次都不会尝试只添加最后一行,它实际上是在每次迭代重复相当多的工作。为避免这种情况,您需要使用更像 Gordon 的方法,但在扫描输入表时另外过滤前一个深度字段,因此您只加入了最近的行。在实践中,这通常不是必需的,但对于非常大的数据集或您无法创建适当的索引时,这可能是一个问题。

在 CTE 的 PostgreSQL 文档中可以了解更多信息。

于 2013-06-23T23:27:50.177 回答
26

这是使用递归 CTE 的 SQL:

with recursive tr(id1, id2, level) as (
      select t.id1, t.id2, 1 as level
      from t union all
      select t.id1, tr.id2, tr.level + 1
      from t join
           tr
           on t.id2 = tr.id1
     )
select *
from (select tr.*,
             max(level) over (partition by id1) as maxlevel
      from tr
     ) tr
where level = maxlevel;

是 SQLFiddle

于 2013-06-23T15:08:54.363 回答