检测递归查询中的循环

时间:2014-08-08 00:55:10

标签: sql postgresql common-table-expression

我的PostgreSQL数据库中有一个有向图,节点和周期之间可以有多条路径:

create table "edges" ("from" int, "to" int);
insert into "edges" values (0, 1), (1, 2), (2, 3), (3, 4), (1, 3);
insert into "edges" values (10, 11), (11, 12), (12, 11);

我希望找到节点与连接到它的每个节点之间的最小边数:

with recursive "nodes" ("node", "depth") as (
  select 0, 0
union
  select "to", "depth" + 1
  from "edges", "nodes"
  where "from" = "node"
) select * from "nodes";

返回所有路径的深度:

 node  0 1 2 3 3 4 4 
 depth 0 1 2 2 3 3 4

 0 -> 1 -> 2 -> 3 -> 4
 0 -> 1 ------> 3 -> 4

我需要最低限度,但是 递归查询的递归术语中不允许使用聚合函数

with recursive "nodes" ("node", "depth") as (
  select 0, 0
union
  select "to", min("depth") + 1
  from "edges", "nodes"
  where "from" = "node"
  group by "to"
) select * from "nodes";

虽然在结果上使用聚合函数:

with recursive "nodes" ("node", "depth") as (
  select 0, 0
union all
  select "to", "depth" + 1
  from "edges", "nodes"
  where "from" = "node"
) select * from (select "node", min("depth")
                 from "nodes" group by "node") as n;

按预期退货

node  0 1 2 3 4
depth 0 1 2 2 3

但是,进入循环会导致无限循环,并且递归引用查询"节点"不得出现在子查询中,因此我无法检查是否已访问过某个节点:

with recursive "nodes" ("node", "depth") as (
  select 10, 0
union
  select "to", "depth" + 1
  from "edges", "nodes"
  where "from" = "node"
  and "to" not in (select "node" from "nodes")
) select * from "nodes";

我在这里寻找的结果是

node  10 11 12
depth  0  1  2

有没有办法用递归查询/公用表表达式来做到这一点?

另一种方法是创建一个临时表,迭代地添加行直到用尽;即先呼吸一次。

相关:this answer检查节点是否已包含在路径中并避免循环,但仍然无法避免执行不必要的工作来检查长度超过最短已知路径的路径,因为它& #39; s仍然表现得像深度优先搜索

1 个答案:

答案 0 :(得分:1)

您可以根据documentation

在查询中添加周期检测
with recursive "nodes" ("node", "depth", "path", "cycle") as (
  select 10, 0, ARRAY[10], false
union all
  select "to", "depth" + 1, path || "to", "to" = ANY(path)
  from "edges", "nodes"
  where "from" = "node" and not "cycle"
) select * from (select "node", min("depth"), "path", "cycle"
                from "nodes" group by "node", "path", "cycle") as n 
                where not "cycle";

此查询将返回您预期的数据