描述
在我们的问题域中,我们正在研究一组连接在一起形成图形的边。 从给定节点(或节点)开始,我们必须列出整个图中连接到给定节点(或节点)的所有链接。 我们必须从左到右,从上到下显示这些链接。
对于具有有限循环次数的图形,我们对此问题进行了查询。更多的循环会以指数方式增加执行时间。
我们需要在递归期间限制对同一节点的访问,以便有一个可行的解决方案。
下面的示例只包含一个循环,但是这个循环已经导致了86个额外的和过时的行。
在类似的帖子中,使用ROW和ANY运算符为postgresql提供了解决方案,但我找不到Oracle解决方案。
我们正在寻找解决方案的替代方案或限制访问相同边缘的方式。
非常感谢任何帮助!
类似
Visiting a directed graph as if it were an undirected one, using a recursive query在postgresql中提供了一个解决方案。 我们需要使用Oracle11g。
示例
边
A-B, B-D, C-A, C-E, C-F, H-F, E-B, G-D, G-I
图形
A
/ \
C - E - B - D
\ /
H - F G - I
DDL和DML
CREATE TABLE EDGE (
FROM_ID VARCHAR(10),
TO_ID VARCHAR(10)
);
INSERT INTO EDGE VALUES ('A', 'B');
INSERT INTO EDGE VALUES ('E', 'B');
INSERT INTO EDGE VALUES ('C', 'E');
INSERT INTO EDGE VALUES ('C', 'A');
INSERT INTO EDGE VALUES ('C', 'F');
INSERT INTO EDGE VALUES ('B', 'D');
INSERT INTO EDGE VALUES ('G', 'D');
INSERT INTO EDGE VALUES ('H', 'F');
INSERT INTO EDGE VALUES ('G', 'I');
输入
nodes: 'A'
必需输出
C A
C E
C F
H F
A B
E B
B D
G D
G I
当前解决方案
我们当前的解决方案完全返回我们需要的内容,但如上所述,每个额外的循环都会以指数方式增加执行时间。
SELECT
c.LVL,
c.FROM_ID,
c.TO_ID,
CASE
WHEN lag(C.TO_ID)
OVER (
PARTITION BY C.LVL
ORDER BY C.LVL, C.TO_ID ) = C.TO_ID
THEN C.LVL || '-' || C.TO_ID
WHEN lead(C.TO_ID)
OVER (
PARTITION BY C.LVL
ORDER BY C.LVL, C.TO_ID ) = C.TO_ID
THEN C.LVL || '-' || C.TO_ID
ELSE C.LVL || '-' || C.FROM_ID
END GROUP_ID
FROM (
WITH chain(LVL, FROM_ID, TO_ID ) AS (
SELECT
1 LVL,
root.FROM_ID FROM_ID,
root.TO_ID TO_ID
FROM EDGE root
WHERE root.TO_ID IN (:nodes)
OR (root.FROM_ID IN (:nodes) AND NOT EXISTS(
SELECT *
FROM EDGE
WHERE TO_ID IN (:nodes)
))
UNION ALL
SELECT
LVL +
CASE
WHEN previous.TO_ID = the_next.FROM_ID
THEN 1
WHEN previous.TO_ID = the_next.TO_ID
THEN 0
WHEN previous.FROM_ID = the_next.FROM_ID
THEN 0
ELSE -1
END LVL,
the_next.FROM_ID FROM_ID,
the_next.TO_ID TO_ID
FROM EDGE the_next
JOIN chain previous ON previous.TO_ID = the_next.FROM_ID
OR the_next.TO_ID = previous.FROM_ID
OR (previous.TO_ID = the_next.TO_ID AND previous.FROM_ID <> the_next.FROM_ID)
OR (previous.TO_ID <> the_next.TO_ID AND previous.FROM_ID = the_next.FROM_ID)
)
SEARCH BREADTH FIRST BY FROM_ID SET ORDER_ID
CYCLE FROM_ID, TO_ID SET CYCLE TO 1 DEFAULT 0
SELECT
C.*,
row_number()
OVER (
PARTITION BY LVL, FROM_ID, TO_ID
ORDER BY ORDER_ID ) rank
FROM chain C
ORDER BY LVL, FROM_ID, TO_ID
) C
WHERE C.rank = 1;
答案 0 :(得分:2)
为了使遍历算法不会返回已经访问过的边缘,可以确实将访问边缘保持在某处。正如您已经发现的那样,您通过字符串连接获得了很多成功。但是,还有其他可用的&#34;值连接&#34;可用的技术......
您必须拥有一个方便的架构级别标量集合:
create or replace type arr_strings is table of varchar2(64);
然后您可以在每次迭代中收集到该集合的访问边缘:
with nondirected$ as (
select from_id, to_id, from_id||'-'||to_id as edge_desc
from edge
where from_id != to_id
union all
select to_id, from_id, from_id||'-'||to_id as edge_desc
from edge
where (to_id, from_id) not in (
select from_id, to_id
from edge
)
),
graph$(lvl, from_id, to_id, edge_desc, visited_edges) as (
select 1, from_id, to_id, edge_desc,
arr_strings(edge_desc)
from nondirected$ R
where from_id in (&nodes)
--
union all
--
select
lvl+1,
Y.from_id, Y.to_id, Y.edge_desc,
X.visited_edges multiset union arr_strings(Y.edge_desc)
from graph$ X
join nondirected$ Y
on Y.from_id = X.to_id
where not exists (
select 1
from table(X.visited_edges) Z
where Y.edge_desc = Z.column_value
)
)
search breadth first by edge_desc set order_id
cycle edge_desc set is_cycle to 1 default 0,
ranked_graph$ as (
select C.*,
row_number() over (partition by edge_desc order by lvl, order_id) as rank$
from graph$ C
-- where is_cycle = 0
)
select *
from ranked_graph$
--where rank$ <= 1
order by lvl, order_id
;
备注的
union
将有向图预处理为非定向图,将一组反向边缘输入到输入。这应该使递归遍历谓词更容易阅读。仅仅是为了我更容易阅读和编写SQL的目的。当然,你不必这样做。将重新访问的边界限制为零
在SQL中,你不能。你提到的PostgreSQL解决方案确实做到了。但是,在Oracle中,您无法做到。对于每个遍历连接,您必须测试所有其他遍历连接的行。这意味着某种聚合或分析...... Oracle禁止并抛出ORA异常。
PLSQL救援?
但是,您可以在PL / SQL中执行此操作。它应该具有多大的性能,取决于你想要从DB预取边缘需要花费多少内存,以及你愿意从#34;当前&#34;中走过图表的SQL往返次数。节点或者如果您愿意使用更多内存来保持受访节点处于花哨的索引边缘集合中,而不是反对加入常规arr_output
集合l_visited_nodes
。你有多种选择,明智地选择。
无论如何,对于使用较多SQL引擎的最简单方案,这可能是您正在寻找的代码......
create or replace
package pkg_so_recursive_traversal
is
type rec_output is record (
from_id edge.from_id%type,
to_id edge.to_id%type,
lvl integer
);
type arr_output is table of rec_output;
function traverse_a_graph
( i_from in arr_strings
, i_is_directed in varchar2 default 'NO' )
return arr_output
pipelined;
end pkg_so_recursive_traversal;
/
create or replace
package body pkg_so_recursive_traversal
is
function traverse_a_graph
( i_from in arr_strings
, i_is_directed in varchar2 )
return arr_output
pipelined
is
l_next_edges arr_output;
l_current_edges arr_output;
l_visited_edges arr_output := arr_output();
l_out rec_output;
i pls_integer;
l_is_directed varchar2(32) := case when i_is_directed = 'YES' then 'YES' else 'NO' end;
begin
select E.from_id, E.to_id, 0
bulk collect into l_next_edges
from table(i_from) F
join edge E
on F.column_value in (E.from_id, case when l_is_directed = 'YES' then null else E.to_id end)
where E.from_id != E.to_id;
l_out.lvl := 0;
loop
dbms_output.put_line(l_next_edges.count());
exit when l_next_edges.count() <= 0;
l_out.lvl := l_out.lvl + 1;
-- spool the edges to output
i := l_next_edges.first();
while i is not null loop
l_out.from_id := l_next_edges(i).from_id;
l_out.to_id := l_next_edges(i).to_id;
pipe row(l_out);
i := l_next_edges.next(i);
end loop;
l_current_edges := l_next_edges;
l_visited_edges := l_visited_edges multiset union l_current_edges;
-- find next edges
select unique E.from_id, E.to_id, 0
bulk collect into l_next_edges
from table(l_current_edges) CE
join edge E
on CE.to_id in (E.from_id, case when l_is_directed = 'YES' then null else E.to_id end)
or l_is_directed = 'NO' and CE.from_id in (E.from_id, E.to_id)
where E.from_id != E.to_id
and not exists (
select 1
from table(l_visited_edges) VE
where VE.from_id = E.from_id
and VE.to_id = E.to_id
);
end loop;
return;
end;
end pkg_so_recursive_traversal;
/
当调用A
的起始节点并考虑图表是无向的时......
select *
from table(pkg_so_recursive_traversal.traverse_a_graph(
i_from => arr_strings('A'),
i_is_directed => 'NO'
));
......它产生......
FROM_ID TO_ID LVL
---------- ---------- ----------
A B 1
C A 1
C E 2
B D 2
C F 2
E B 2
G D 3
H F 3
G I 4
备注的
edge
表执行多次(对于您的示例输入正好为5)SQL往返。与具有冗余边缘访问的纯SQL解决方案相比,这可能会或可能不会成为更大的性能损失。正确测试更多解决方案,看看哪一个最适合你。rec_output
和arr_output
类型。