我在postgres中的表如下所示,表存储了ID之间的链式关系,我想要一个可以产生结果的查询,如“vc1” - > “rc7”或“vc3” - >“rc7”,我只会查询第一列ID1中的ID
ID1 ID2
"vc1" "vc2"
"vc2" "vc3"
"vc3" "vc4"
"vc4" "rc7"
所以我想在这里提供一些“head”id,我必须获取尾部(链中的最后一个)id。
答案 0 :(得分:24)
这是对PostgreSQL 8.4及更高版本中提供的简单递归公用表表达式(WITH RECURSIVE
)的经典用法。
在此演示: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' reference为null并产生零行,这导致迭代结束。
外部查询然后通过不存在的to_id来选择已被确定为链的末尾的行。
需要花费一些精力来了解递归CTE。他们要理解的关键事项是:
它们从初始查询的输出开始,它们反复与"递归部分"的输出结合。 (UNION
或UNION ALL
之后的查询),直到递归部分不添加任何行。这会停止迭代。
它们并不是真正的递归,更具迭代性,尽管它们对于你可能使用递归的各种东西都有好处。
所以你基本上是在一个循环中构建一个表。您无法删除行或更改行,只能添加新行,因此您通常需要一个外部查询来过滤结果以获取所需的结果行。您经常会添加包含中间数据的额外列,用于跟踪迭代状态,控制停止条件等。
有助于查看未经过滤的结果。如果我用简单的SELECT * FROM chain
替换最终的摘要查询,我可以看到生成的表格:
from_id | to_id
---------+-------
| vc2
vc2 | vc3
vc3 | vc4
vc4 | rc7
rc7 |
(5 rows)
第一行是手动添加的起点行,您可以在其中指定要查找的内容 - 在本例中为vc2
。每个后续行都是由UNION
ed递归术语添加的,该术语在前一个结果上执行LEFT OUTER JOIN
并返回一组与上一个to_id
配对的新行(现在在{ {1}}列)到下一个from_id
。如果to_id
没有匹配,则LEFT OUTER JOIN
将为空,导致下一次调用现在返回行并结束迭代。
因为此查询每次都不会尝试仅添加 last 行,所以它实际上每次迭代都会重复一些工作。为了避免这种情况,您需要使用更像Gordon的方法,但是当您扫描输入表时,还要在前一个深度字段上进行过滤,因此您只加入了最新的行。在实践中,这通常是不必要的,但它可能是非常大的数据集的关注点,或者您无法创建适当的索引。
可以在the PostgreSQL documentation on CTEs中学习更多内容。
答案 1 :(得分:17)
以下是使用递归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;
Here是SQLFiddle