【发布时间】:2012-02-04 14:06:23
【问题描述】:
关于访问存储在数据库中的有向图,我需要您的帮助。
考虑以下有向图
1->2
2->1,3
3->1
一个表存储这些关系:
create database test;
\c test;
create table ownership (
parent bigint,
child bigint,
primary key (parent, child)
);
insert into ownership (parent, child) values (1, 2);
insert into ownership (parent, child) values (2, 1);
insert into ownership (parent, child) values (2, 3);
insert into ownership (parent, child) values (3, 1);
我想提取从节点可达的图形的所有半连通边(即忽略方向的连通边)。即,如果我从 parent=1 开始,我希望得到以下输出
1,2
2,1
2,3
3,1
我正在使用 postgresql。
我已经修改了解释递归查询的example on Postgres' manual,并且我已经将连接条件调整为“向上”和“向下”(这样做我忽略了方向)。我的查询如下:
\c test
WITH RECURSIVE graph(parent, child, path, depth, cycle) AS (
SELECT o.parent, o.child, ARRAY[ROW(o.parent, o.child)], 0, false
from ownership o
where o.parent = 1
UNION ALL
SELECT
o.parent, o.child,
path||ROW(o.parent, o.child),
depth+1,
ROW(o.parent, o.child) = ANY(path)
from
ownership o, graph g
where
(g.parent = o.child or g.child = o.parent)
and not cycle
)
select g.parent, g.child, g.path, g.cycle
from
graph g
它的输出如下:
parent | child | path | cycle
--------+-------+-----------------------------------+-------
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 rows)
我有一个问题:查询多次提取相同的边,因为它们是通过不同的路径到达的,我想避免这种情况。如果我将外部查询修改为
select distinct g.parent, g.child from graph
我得到了想要的结果,但是 WITH 查询仍然效率低下,因为不需要的连接已经完成。 那么,有没有一种解决方案可以在不使用 distinct 的情况下,从给定的边开始提取 db 中图的可达边?
我还有另一个问题(这个问题解决了,看底部):从输出中可以看出,循环只有在第二次到达节点时才会停止。 IE。我有(1,2) (2,3) (1,2)。
我想在再次循环最后一个节点之前停止循环,即拥有(1,2) (2,3)。
我尝试修改 where 条件如下
where
(g.parent = o.child or g.child = o.parent)
and (ROW(o.parent, o.child) <> any(path))
and not cycle
为了避免访问已经访问过的边缘,但它不起作用,我不明白为什么 ((ROW(o.parent, o.child) <> any(path)) 在再次进入循环边缘之前应该避免循环但不起作用)。 如何在关闭循环的节点前一步停止循环?
编辑:按照 danihp 的建议,解决我使用的第二个问题
where
(g.parent = o.child or g.child = o.parent)
and not (ROW(o.parent, o.child) = any(path))
and not cycle
现在输出不包含循环。行从 13 变为 6,但我仍然有重复,因此提取所有边缘而没有重复且没有不同的主要(第一个)问题仍然存在。电流输出与and not ROW
parent | child | path | cycle
--------+-------+---------------------------+-------
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 rows)
编辑 #2::按照 Erwin Brandstetter 的建议,我修改了我的查询,但如果我没记错的话,建议的查询提供的行数比我的多( ROW 比较仍然存在,因为它对我来说似乎更清楚,即使我知道字符串比较会更有效)。 使用新查询,我得到 20 行,而我的得到 6 行
WITH RECURSIVE graph(parent, child, path, depth) AS (
SELECT o.parent, o.child, ARRAY[ROW(o.parent, o.child)], 0
from ownership o
where 1 in (o.child, o.parent)
UNION ALL
SELECT
o.parent, o.child,
path||ROW(o.parent, o.child),
depth+1
from
ownership o, graph g
where
g.child in (o.parent, o.child)
and ROW(o.parent, o.child) <> ALL(path)
)
select g.parent, g.child from graph g
编辑 3:所以,正如 Erwin Brandstetter 所指出的,最后一个查询仍然是错误的,而正确的查询可以在他的答案中找到。
当我发布我的第一个查询时,我不明白我缺少一些连接,因为它发生在以下情况:如果我从节点 3 开始,数据库选择行 (2,3) 和 (3,1) .然后,查询的第一个归纳步骤将从这些行中选择并连接行(1,2)、(2,3) 和(3,1),缺少应该包含在结果中的行 (2,1) 作为概念上的算法将暗示((2,1) 是“接近”(3,1))
当我尝试修改 Postgresql 手册中的示例时,我尝试加入 ownership 的父母和孩子是正确的,但我没有保存每个步骤中必须加入的 graph 的值是错误的。
这些类型的查询似乎会根据起始节点生成不同的行集(即取决于在基本步骤中选择的行集)。因此,我认为在基本步骤中仅选择包含起始节点的一行可能会很有用,因为无论如何您都会得到任何其他“相邻”节点。
【问题讨论】:
-
看起来像地图缩减问题
-
你试过
and NOT (ROW(o.parent, o.child) = any(path))吗?还不够:select distinct g.parent, g.child from graph g where cycle = f? -
谢谢danihp,“而不是ROW”有效!所以第二个问题解决了! (将更新问题)。行数从 13 到 6!但我仍然有重复。你是对的,使用 distinct 输出将是我需要的,但一些不需要的连接仍会在 WITH 查询中,如果你有一个大图,这可能是一个性能问题。我希望尽可能地提高效率,而不是 - 如果可能的话 - 重复,甚至不使用 distinct
-
你说它是一个“有向图”,但你也是从子图遍历到父图?这是故意的吗?
-
顺便说一句,更简单的语法:
(ROW(o.parent, o.child) <> ALL(path))而不是NOT (ROW(o.parent, o.child) = ANY(path))。
标签: sql postgresql recursion graph common-table-expression