假设我有一个名为graph的表格:

_id relates
1 {2, 3}
2 {4}
3 {5, 6}
4 {3, 7}
5 {}
6 {}
7 {}

以下是图表形式: graph

我的问题是编写一个递归查询来查找两个 node 之间的最短路径.在本例中,我的目标是找到 node 1和 node 3之间的最短路径.

下面的查询运行良好,并解决了问题.

    with recursive pathFrom1to3(_id, relates, lvl, _path) as
        (
            select _id, relates, 1 as lvl, ARRAY[_id]
            from graph
            where _id = 1
        union
            select g._id, g.relates, p.lvl+1 as lvl, _path || g._id
            from pathFrom1to3 p join graph g on g._id = any(p.relates)
            where not g._id = any(p._path) -- handles cycles (not applicable for this example)
        )
    
    select * from pathFrom1To3
    where _id = 3
    limit 1

递归查询实际上从 node 1开始查找all条可能的路径,直到它找不到更多的路径,因此在递归结束后,我们只剩下这个表:

_id relates lvl _path
1 {2, 3} 1 {1}
2 {4} 2 {1, 2}
3 {5, 6} 2 {1, 3}
4 {3, 7} 3 {1, 2, 4}
5 {} 3 {1, 3, 5}
6 {} 3 {1, 3, 6}
3 {5, 6} 4 {1, 2, 4, 3}
7 {} 4 {1, 2, 4, 7}
5 {} 5 {1, 2, 4, 3, 5}
6 {} 5 {1, 2, 4, 3, 6}

在我们筛选出_id = 3个之后,我们得到:

_id relates lvl _path
3 {5, 6} 2 {1, 3}
3 {5, 6} 4 {1, 2, 4, 3}

通常情况下,最短的路径是我们最先击中的路径(因为边没有权重).按照我们查询的逻辑,这将等同于最早返回的记录,因此我们可以对此使用Limit:Limit 1

_id relates lvl _path
3 {5, 6} 2 {1, 2, 4}

并且存在从 node 1到 node 3的最短路径.

我的问题是,该查询计算从 node 1开始的all条路径,如果目标 node 接近搜索开始,则效率非常低.我的目标是让查询在命中目标 node 后立即(完全)停止搜索,因为我们知道不会有其他最短路径.

I need a way to terminate the recursion completely when node 3 is reached.

如果我try 为 node 本身添加终止符,如下面的查询,则相同级别的其他 node 的递归将继续,因为终止条件仍然满足:

    with recursive pathFrom1to3(_id, relates, lvl, _path) as
        (
            select _id, relates, 1 as lvl, ARRAY[_id]
            from graph
            where _id = 1
        union
            select g._id, g.relates, p.lvl+1 as lvl, _path || g._id
            from pathFrom1to3 p join graph g on g._id = any(p.relates)
            where not g._id = any(p._path) -- handles cycles (not applicable for this example)
            **and not p._id = 3**
        )
    
    select * from pathFrom1To3

产生:

_id relates lvl _path
1 {2, 3} 1 {1}
2 {4} 2 {1, 2}
3 {5, 6} 2 {1, 3}
4 {3, 7} 3 {1, 2, 4}
3 {5, 6} 4 {1, 2, 4, 3}
7 {} 4 {1, 2, 4, 7}
5 {} 5 {1, 2, 4, 3, 5}
6 {} 5 {1, 2, 4, 3, 6}

注意,当第一次到达 node 3时,递归停止;不会进一步搜索 node 5和6,因为 node 3被击中.但是对于 node 4(从 node 2)递归继续,因为p.id不是3,而是2.

当递归到达 node 3for the entire level时,我们需要一种方法来终止递归.

我的 idea 是创建一个reached列,当_id不是3时为0,当_id为3时为1. 然后我们可以使用SUM来判断整个级别达到的值的总和,如果它不是0,那么我们将终止,但我正在努力将其作为一个查询来编写.

以下是我写这篇文章的try :

    with recursive pathFrom1to3(_id, relates, lvl, _path, reached, lvlReached) as
        (
            select _id, relates, 1 as lvl, ARRAY[_id], 0 as reached, 0 as lvlReached
            from graph
            where _id = 1
        
        union
        
            select _id, relates, lvl, _path, reached, lvlReached from
        
            (
                select g._id, g.relates, p.lvl+1 as lvl, _path || g._id, 
                       case when 3 = any(g.relates) then 1 else 0 end as reached
                from pathFrom1to3 p join graph g on g._id = any(p.relates)
                where not g._id = any(p._path) -- handles cycles
            ) mainRecursion
        
            join
        
            (
                select lvl, sum(reached) as lvlReached
                from pathFrom1To3
                group by lvl
            ) lvlReached
        
            on mainRecursion.lvl = lvlReach.lvl
            where lvlReached = 0 
        
        )
    
    select * from pathFrom1To3

这给了我一个错误:

    recursive reference to query "pathFrom1to3" must not appear more than once.

推荐答案

我们可以try 一个窗口函数来保持指示找到解的状态,从而导致所有进一步的递归迭代被修剪.

The fiddle

WITH RECURSIVE cte(id, vals, relates, done) AS (
   SELECT g._id, array[g._id], relates, null::int
     FROM graph AS g
    WHERE _id = 1
    UNION ALL
   SELECT g._id, vals||g._id, g.relates
        , MAX(CASE WHEN 3 IN (done, g._id) THEN 3 END) OVER ()
     FROM cte   AS c
     JOIN graph AS g
       ON g._id     = any(c.relates)
      AND NOT g._id = any(c.vals)   -- Avoid cycles
      AND done IS NULL              -- wait until we're done
     )
SELECT * FROM cte
 WHERE id = 3
;

结果是:

id vals relates done
3 {1,3} {5,6} 3

通过注释掉最后一个查询表达式中的id = 3逻辑,我们可以看到所有生成的行:

id vals relates done
1 {1} {2,3} null
2 {1,2} {4} 3
3 {1,3} {5,6} 3

Sql相关问答推荐

如何将varchar传递给tvf并使用该参数来查询结果SQL服务器

基于列对多行求和的查询

如何以"% m—% d"格式对生日列表进行排序,以查找与今天最近的日期?

SQL -滞后于上一个非重复值

查询页面推荐

数据库SQL-CTE命名空间(错误?)使用临时视图

Oracle 23c ROUND,数据类型为DATE

Oracle PL/SQL:解决DBMS输出大小限制的问题

具有分组条件的不同计数 (DAX)

PostgreSQL:从多个字段收集特定指标的最后一个条目

获取多个开始-结束时间戳集之间经过的时间

如何在 JSONB 数组的每个对象中添加新的键值对- PostgreSQL

JSON对象查询SQL服务器

多行状态下的分组查询判断状态

如何通过CROSS APPLY获取多级嵌套JSON属性的值?

使用in和and运算符过滤记录的条件

使用 PL/PGSQL 函数 Select 返回多条记录

如何比较同一张表中的行并进行相应更新

从不同的表中 Select 包含单词列表的记录

从 Pyspark 转换为具有多个分组条件的语句时的情况