使用递归查询来访问有向图,就好像它是无向图 [英] Visiting a directed graph as if it were an undirected one, using a recursive query

查看:141
本文介绍了使用递归查询来访问有向图,就好像它是无向图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



考虑以下有向图

  1-> 2 
2-> 1,3
3-> 1

表存储这些关系:

  create database test ; 
\c test;

创建表所有权(
父级bigint,
子级bigint,
主键(父级,子级)
);

插入所有权(父,子)值(1,2);
插入所有权(父,子)值(2,1);
插入所有权(父,子)值(2,3);
插入所有权(父,子)值(3,1);

我想提取所有半连接边(即连接边忽略方向)从节点可到达的图形。也就是说,如果我从parent = 1开始,我想要有以下输出:

  1,2 
2,1
2,3
3,1

我正在使用 postgresql



我修改了上的例子,它解释了递归查询,并且我已经调整了连接条件以上和下(这样做,我忽略了方向)。我的查询如下:

  \c测试

带有RECURSIVE图(父,子(
SELECT o.parent,o.child,ARRAY [ROW(o.parent,o.child)],0,false
所有权o
where o.parent = 1
UNION ALL
SELECT
o.parent,o.child,
path || ROW(o.parent,o.child),
深度+ 1,
ROW(o.parent,o.child)= ANY(路径)

所有权o,图g
其中
(g。 parent = o.child或g.child = o.parent)
并且不循环


选择g.parent,g.child,g.path,g.cycle
from
graph g

其输出如下:

 父母|孩子|路径|循环
-------- + ------- + ---------------------------- ------- + -------
1 | 2 | {(1,2)} | f
2 | 1 | {(1,2),(2,1)} | f
2 | 3 | {(1,2),(2,3)} | f
3 | 1 | {(1,2),(3,1)} | f
1 | 2 | {(1,2),(2,1),(1,2)} | t
1 | 2 | {(1,2),(2,3),(1,2)} | t
3 | 1 | {(1,2),(2,3),(3,1)} | f
1 | 2 | {(1,2),(3,1),(1,2)} | t
2 | 3 | {(1,2),(3,1),(2,3)} | f
1 | 2 | {(1,2),(2,3),(3,1),(1,2)} | t
2 | 3 | {(1,2),(2,3),(3,1),(2,3)} | t
1 | 2 | {(1,2),(3,1),(2,3),(1,2)} | t
3 | 1 | {(1,2),(3,1),(2,3),(3,1)} | t
(13行)

我有一个问题查询会多次提取相同的边,因为它们是通过不同的路径到达的,我想避免这种情况。如果我将外部查询修改为

 ,请从图

我得到了期望的结果,但是由于不需要的连接完成,所以WITH查询中仍然存在效率低下的问题。
那么,是否有解决方案可以从一个给定的数据库中提取图形的可到达边缘,而不使用不同的?



我还遇到了另一个问题(这个问题已解决,请查看底部):正如您从输出中看到的那样,仅当第二次到达节点时,循环才会停止。即我有(1,2)(2,3)(1,2)
我想在再次循环最后一个节点之前停止循环,即有(1,2)(2,3)
我试图修改where条件,如下所示:

 其中
(g。 parent = o.child或g.child = o.parent)
和(ROW(o.parent,o.child)<>任何(路径))
并且不循环

避免访问已经访问过的边,但它不起作用,我无法理解为什么( (ROW(o.parent,o.child)<>任何(路径))应该避免循环再次进入循环边缘,但不起作用)。 如何才能在关闭循环的节点之前停止循环?


$ b

编辑 :正如danihp所建议的那样,为了解决我使用的第二个问题

 其中
(g.parent = o.child或g.child = o.parent)
而不是(ROW(o.parent,o.child)= any(path))
并且不循环

现在输出不包含循环。行从13到6,但我仍然有重复,所以提取所有没有重复和没有重复的边的主要(第一个)问题仍然存在。当前输出而不是ROW

 父母|孩子|路径|循环
-------- + ------- + --------------------------- + -------
1 | 2 | {(1,2)} | f
2 | 1 | {(1,2),(2,1)} | f
2 | 3 | {(1,2),(2,3)} | f
3 | 1 | {(1,2),(3,1)} | f
3 | 1 | {(1,2),(2,3),(3,1)} | f
2 | 3 | {(1,2),(3,1),(2,3)} | f
(6行)

编辑#2: em::按照Erwin Brandstetter的建议,我修改了我的查询,但是如果我没有错,建议的查询会给出比我更多的行(行比较仍然存在,因为它对我来说似乎更清楚,即使是我了解字符串比较会更有效率)。
使用新的查询,我获得了20行,而我的行有6行

  WITH RECURSIVE图(父,子,路径,深度)AS(
SELECT o.parent,o.child,ARRAY [ROW(o.parent,o.child)],0
从所有权o
where 1 in( o.child,o.parent)
UNION ALL
SELECT
o.parent,o.child,
path || ROW(o.parent,o.child),
深度+ 1

所有权o,图g
其中
g.child在(o.parent,o.child)
和ROW(o .parent,o.child)<> ALL(路径)


从图中选择g.parent,g.child g

编辑3 :正如Erwin Brandstetter指出的那样,而在他的回答中可以找到正确的一个。

当我发布我的第一个查询时,我并没有理解我错过了一些连接,因为它发生在以下情况:如果我从节点3开始,数据库选择行(2,3)(3,1)。然后,查询的第一个归纳步骤将从这些行中选择行(1,2)(2,3)(3,1),缺少应包含在结果中的行(2,1),因为概念上算法意味着((2,1)是near(3,1)



当我尝试修改Postgresql手册中的示例时,我正确地尝试加入所有权的父级和子级,但我错误地未保存必须在每一步中加入。



这些类型的查询似乎会生成一组不同的行取决于起始节点(即取决于在基本步骤中选择的行集)。所以,我认为在基本步骤中只选择一行包含起始节点是有用的,因为无论如何你会得到任何其他的相邻节点。

解决方案

可以这样工作:

  WITH RECURSIVE graph AS(
SELECT parent
,child
,','|| parent :: text ||','|| child :: text ||','AS path
,0 AS depth
FROM ownership
WHERE parent = 1

UNION ALL
SELECT o.parent
,o.child
,g.path || o.child | |','
,g.depth + 1
FROM图g
JOIN所有权o ON o.parent = g.child
WHERE g.path!~~(' ,'|| o.parent :: text ||','|| o.child :: text ||',%')

SELECT *
FROM graph

您提到了性能,所以我在这方面进行了优化。



< h3>主要观点:

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆