假设我有下面的表,名为 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.
2条答案
按热度按时间qxgroojn1#
我们可以尝试使用一个窗口函数来保存状态,指示找到了解,从而导致所有进一步的递归迭代被修剪。
The fiddle
结果是:
| 身份证|瓦尔斯|联系|完成|
| - ------|- ------|- ------|- ------|
| 三个|{1、3}|{五、六}|三个|
通过注解掉最后一个查询表达式中的
id = 3
逻辑,我们可以看到生成的所有行:| 身份证|瓦尔斯|联系|完成|
| - ------|- ------|- ------|- ------|
| 1个|{1}|{2、3}|零|
| 第二章|{1、2}|{四}|三个|
| 三个|{1、3}|{五、六}|三个|
zaq34kh62#
您可以自下而上搜索,从
3
到1
反向搜索,从而减少搜索空间:See fiddle.