[英]Postgresql recursive self join
我在postgres中的表如下所示,表存储ID之间的链式关系,我希望有一个查询可以产生结果,如“vc1” - >“rc7”或“vc3” - >“rc7”,我会仅查询第一列ID1中的ID
ID1 ID2
"vc1" "vc2"
"vc2" "vc3"
"vc3" "vc4"
"vc4" "rc7"
所以我想在这里提供一些“头”ID,我必须获取尾部(链中的最后一个)id。
这是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”引用不存在的行时,它将为该行添加一个null''引用。 下一次迭代将注意到'to'引用为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
(现在在from_id
列中)与下一个to_id
。 如果LEFT OUTER JOIN
不匹配,则to_id
将为null,导致下一次调用现在返回行并结束迭代。
因为此查询不会尝试每次只添加最后一行,所以实际上每次迭代都会重复一些工作。 为了避免这种情况,您需要使用更像Gordon的方法,但是当您扫描输入表时,还要在前一个深度字段上进行过滤,因此您只加入了最新的行。 在实践中,这通常不是必需的,但它可能是非常大的数据集或无法创建适当索引的问题。
可以在关于CTE的PostgreSQL文档中学到更多。
这是使用递归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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.