我在 postgres 中的表如下所示,表存储了 ID 之间的链式关系,我想要一个可以产生类似“vc1”->“rc7”或“vc3”->“rc7”的结果的查询,我会仅查询第一列 ID1 中的 ID
ID1 ID2
"vc1" "vc2"
"vc2" "vc3"
"vc3" "vc4"
"vc4" "rc7"
所以我想在这里提供一些“head” id,我必须为其获取tail(链中的最后一个)id。
我在 postgres 中的表如下所示,表存储了 ID 之间的链式关系,我想要一个可以产生类似“vc1”->“rc7”或“vc3”->“rc7”的结果的查询,我会仅查询第一列 ID1 中的 ID
ID1 ID2
"vc1" "vc2"
"vc2" "vc3"
"vc3" "vc4"
"vc4" "rc7"
所以我想在这里提供一些“head” id,我必须为其获取tail(链中的最后一个)id。
这是一个简单的递归公用表表达式 ( 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 需要付出一些努力。他们需要理解的关键是:
它们从初始查询的输出开始,它们反复与“递归部分”(UNION
or之后的查询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
前一个结果执行 a 并返回一组新的行,这些行将前一个to_id
(现在在from_id
列中)与下一个配对to_id
。如果LEFT OUTER JOIN
不匹配,则to_id
将为空,导致下一次调用现在返回行并结束迭代。
因为这个查询每次都不会尝试只添加最后一行,它实际上是在每次迭代重复相当多的工作。为避免这种情况,您需要使用更像 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