postgresql 在整个递归级别上终止带有条件的递归查询- Postgres

68de4m5k  于 2023-01-17  发布在  PostgreSQL
关注(0)|答案(2)|浏览(328)

假设我有下面的表,名为 graph
| _id|relates|
| - ------|- ------|
| 1|{2, 3}|
| 2|{4}|
| 3|{5, 6}|
| 4|{3, 7}|
| 5|x1米11米1x|
| 6|{}|
| 7|{}|
以下是图表形式:graph
我的问题是写一个递归查询来寻找两个节点之间的最短路径。在这个例子中,我的目标是寻找节点1和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

递归查询实际上找到了从节点1开始的所有可能的路径,直到它找不到更多的路径,所以在递归结束后,我们只剩下这个表:
| _id|relates|lvl|_path|
| - ------|- ------|- ------|- ------|
| x1米20英寸1x|{2, 3}|1|{1}|
| 2|{4}|2|{1, 2}|
| 3|{5, 6}|x1米30英寸1x|{1, 3}|
| 4|{3, 7}|3|{1, 2, 4}|
| 5|{}|3|x1米39英寸|
| x1米40英寸1x|{}|3|{1, 3, 6}|
| 3|{5, 6}|4|{1, 2, 4, 3}|
| 7|{}|x1米50英寸|{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:限值1
| _id|relates|lvl|_path|
| - ------|- ------|- ------|- ------|
| 3|{5, 6}|2|x1米80英寸|
并且存在从节点1到3的最短路径。
我的问题是,这个查询计算从节点1开始的所有条路径,如果目标节点靠近搜索的起点,效率会非常低,我的目标是让查询在到达目标节点后立即停止搜索(完全),因为我们知道不会有任何其他最短路径。

我需要一种在到达节点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)
            **and not p._id = 3**
        )
    
    select * from pathFrom1To3

产生:
| _标识符|联系|水平|_路径|
| - ------|- ------|- ------|- ------|
| 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米100英寸1英寸|
| 7|{}|4|{1, 2, 4, 7}|
| 5|{}|5|{1, 2, 4, 3, 5}|
| 6|x1米110英寸1x|5|{1, 2, 4, 3, 6}|
注意,递归在第一次到达节点3时停止;因为节点3被命中,所以节点5和6不被进一步搜索,但是由于p._id不是3,而是2,所以递归继续针对节点4(从节点2)。
我们需要一种方法,当它到达整个层的节点3时终止递归。
我的想法是创建一个 reached 列,当_id不为3时为0,当_id为3时为1。然后我们可以使用SUM检查整个级别的reached值的总和,如果不为0,则终止,但我很难将其编写为查询。
下面是我写作的尝试:

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.
qxgroojn

qxgroojn1#

我们可以尝试使用一个窗口函数来保存状态,指示找到了解,从而导致所有进一步的递归迭代被修剪。
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
;

结果是:
| 身份证|瓦尔斯|联系|完成|
| - ------|- ------|- ------|- ------|
| 三个|{1、3}|{五、六}|三个|
通过注解掉最后一个查询表达式中的id = 3逻辑,我们可以看到生成的所有行:
| 身份证|瓦尔斯|联系|完成|
| - ------|- ------|- ------|- ------|
| 1个|{1}|{2、3}|零|
| 第二章|{1、2}|{四}|三个|
| 三个|{1、3}|{五、六}|三个|

zaq34kh6

zaq34kh62#

您可以自下而上搜索,从31反向搜索,从而减少搜索空间:

with recursive cte(id, vals) as (
   select g._id, array[3, g._id] from graph g where 3 = any(g.relates)
   union all
   select g._id, vals||g._id from cte c join graph g 
   on c.id = any(g.relates) and not g._id = any(c.vals)
)
select * from cte where id = 1

See fiddle.

相关问题