Sql 循环的递归CTE停止条件

Sql 循环的递归CTE停止条件,sql,postgresql,graph,common-table-expression,recursive-query,Sql,Postgresql,Graph,Common Table Expression,Recursive Query,我需要使用递归CTE迭代带有循环的图 问题在于循环部分 如果有循环,我想选择最短路径。 这基本上意味着忽略循环,因为递归是“宽度优先” 下面的示例显示了返回的数据: 问题是注释掉的INSERT语句创建了一个循环。 显然,如果没有注释,查询将永远不会完成 我需要的是返回与没有循环时相同的数据 DROP TABLE IF EXISTS edges; CREATE TABLE edges( src integer, dst integer, data integer ); INSERT

我需要使用递归CTE迭代带有循环的图

问题在于循环部分

如果有循环,我想选择最短路径。 这基本上意味着忽略循环,因为递归是“宽度优先”

下面的示例显示了返回的数据:

问题是注释掉的
INSERT
语句创建了一个循环。 显然,如果没有注释,查询将永远不会完成

我需要的是返回与没有循环时相同的数据

DROP TABLE IF EXISTS edges;

CREATE TABLE edges(
  src integer,
  dst integer,
  data integer
);

INSERT INTO edges VALUES (1, 2, 1);
INSERT INTO edges VALUES (2, 3, 1);
--INSERT INTO edges VALUES (3, 2, 1);  -- This entry creates a loop
INSERT INTO edges VALUES (1, 4, 1);
INSERT INTO edges VALUES (4, 5, 1);
INSERT INTO edges VALUES (5, 2, 1);

INSERT INTO edges VALUES (1, 4, 2);
INSERT INTO edges VALUES (4, 5, 2);
INSERT INTO edges VALUES (4, 6, 2);


WITH RECURSIVE paths AS (
  -- For simplicity assume node 1 is the start
  -- we'll have two starting nodes for data = 1 and 2
  SELECT DISTINCT
    src           as node
    , data        as data
    , 0           as depth
    , src::text   as path
  FROM edges
  WHERE
    src = 1

  UNION ALL

  SELECT DISTINCT
    edges.dst
    , edges.data
    , depth + 1
    , paths.path || '->' || edges.dst::text
  FROM paths
    JOIN edges ON edges.src = paths.node AND edges.data = paths.data
    -- AND eliminate loops?
)

SELECT * FROM paths;
返回:

 node | data | depth |     path      
------+------+-------+---------------
    1 |    1 |     0 | 1
    1 |    2 |     0 | 1
    2 |    1 |     1 | 1->2
    4 |    1 |     1 | 1->4
    4 |    2 |     1 | 1->4
    3 |    1 |     2 | 1->2->3
    5 |    2 |     2 | 1->4->5
    6 |    2 |     2 | 1->4->6
    5 |    1 |     2 | 1->4->5
    2 |    1 |     3 | 1->4->5->2
    3 |    1 |     4 | 1->4->5->2->3
(11 rows)
这里的条件是
而不是paths.path-LIKE“%”| | edges.dst::text | |‘%”
我们避免了会导致循环的后边缘。

处理循环的标准方法是在途中构建一个数组,并检查其中是否存在元素:

WITH RECURSIVE paths AS (
  SELECT DISTINCT
    src           as node
    , data        as data
    , 0           as depth
    , src::text   as path
    , false       as is_cycle
    , ARRAY[src]  as path_array
  FROM edges
  WHERE src IN (1,2)
  UNION ALL
  SELECT DISTINCT
    edges.dst
    , edges.data
    , depth + 1
    , paths.path || '->' || edges.dst::text
    , dst = ANY(path_array)
    , path_array  || dst
  FROM paths
  JOIN edges 
    ON edges.src = paths.node 
    AND edges.data = paths.data
    AND NOT is_cycle
)
SELECT * FROM paths;
输出:

+-------+-------+--------+-------------------+-----------+---------------+
| node  | data  | depth  |       path        | is_cycle  |  path_array   |
+-------+-------+--------+-------------------+-----------+---------------+
|    1  |    1  |     0  | 1                 | f         | {1}           |
|    1  |    2  |     0  | 1                 | f         | {1}           |
|    2  |    1  |     0  | 2                 | f         | {2}           |
|    2  |    1  |     1  | 1->2              | f         | {1,2}         |
|    3  |    1  |     1  | 2->3              | f         | {2,3}         |
|    4  |    1  |     1  | 1->4              | f         | {1,4}         |
|    4  |    2  |     1  | 1->4              | f         | {1,4}         |
|    2  |    1  |     2  | 2->3->2           | t         | {2,3,2}       |
|    3  |    1  |     2  | 1->2->3           | f         | {1,2,3}       |
|    5  |    1  |     2  | 1->4->5           | f         | {1,4,5}       |
|    5  |    2  |     2  | 1->4->5           | f         | {1,4,5}       |
|    6  |    2  |     2  | 1->4->6           | f         | {1,4,6}       |
|    2  |    1  |     3  | 1->2->3->2        | t         | {1,2,3,2}     |
|    2  |    1  |     3  | 1->4->5->2        | f         | {1,4,5,2}     |
|    3  |    1  |     4  | 1->4->5->2->3     | f         | {1,4,5,2,3}   |
|    2  |    1  |     5  | 1->4->5->2->3->2  | t         | {1,4,5,2,3,2} |
+-------+-------+--------+-------------------+-----------+---------------+


PostgreSQL 14将使用两个新子句
SEARCH
CYCLE
扩展递归cte:

CYCLE子句首先指定要跟踪循环检测的列列表,然后指定显示是否已检测到循环的列名,然后指定在该列中用于是和否情况的两个值,最后指定将跟踪路径的另一列的名称。cycle和path列将隐式添加到CTE的输出行中

此处演示使用相同语法(Oracle):


带递归CTE的有向循环图的最短路径遍历。有趣的快速搜索可以找到很多关于这个主题的文章…谢谢你删除了重复的文章,这里有一篇有趣的文章+1,如果我有时间,我会玩这个。我已经有了答案(这是非常明显和简单的),但不会在这里提供:)想看看其他的选择。这大概是我的解决方案。除非你的循环仍然无法进入起始节点。但这很容易解决。不过,在接受之前,我会等待解决方案的其他选项。
+-------+-------+--------+-------------------+-----------+---------------+
| node  | data  | depth  |       path        | is_cycle  |  path_array   |
+-------+-------+--------+-------------------+-----------+---------------+
|    1  |    1  |     0  | 1                 | f         | {1}           |
|    1  |    2  |     0  | 1                 | f         | {1}           |
|    2  |    1  |     0  | 2                 | f         | {2}           |
|    2  |    1  |     1  | 1->2              | f         | {1,2}         |
|    3  |    1  |     1  | 2->3              | f         | {2,3}         |
|    4  |    1  |     1  | 1->4              | f         | {1,4}         |
|    4  |    2  |     1  | 1->4              | f         | {1,4}         |
|    2  |    1  |     2  | 2->3->2           | t         | {2,3,2}       |
|    3  |    1  |     2  | 1->2->3           | f         | {1,2,3}       |
|    5  |    1  |     2  | 1->4->5           | f         | {1,4,5}       |
|    5  |    2  |     2  | 1->4->5           | f         | {1,4,5}       |
|    6  |    2  |     2  | 1->4->6           | f         | {1,4,6}       |
|    2  |    1  |     3  | 1->2->3->2        | t         | {1,2,3,2}     |
|    2  |    1  |     3  | 1->4->5->2        | f         | {1,4,5,2}     |
|    3  |    1  |     4  | 1->4->5->2->3     | f         | {1,4,5,2,3}   |
|    2  |    1  |     5  | 1->4->5->2->3->2  | t         | {1,4,5,2,3,2} |
+-------+-------+--------+-------------------+-----------+---------------+
CYCLE id SET is_cycle TO true DEFAULT false USING path
WITH paths(node, data,depth,path) AS (
  SELECT
    src           as node
    , data        as data
    , 0           as depth
    , TO_CHAR(src)as path
  FROM edges
  WHERE src IN (1,2)
  UNION ALL
  SELECT
    edges.dst
    , edges.data
    , depth + 1
    , paths.path || '->' || edges.dst
  FROM paths
  JOIN edges 
    ON edges.src = paths.node 
    AND edges.data = paths.data
) CYCLE node SET cycle TO 1 DEFAULT 0
SELECT DISTINCT * FROM paths;