查找两个节点之间路径的 Oracle/过程

Posted

技术标签:

【中文标题】查找两个节点之间路径的 Oracle/过程【英文标题】:Oracle/procedure to find path between two nodes 【发布时间】:2018-01-24 14:58:00 【问题描述】:

我还是 Oracle 的新手,需要以下帮助:

假设我有一个包含节点的表:

DECLARE @nodes TABLE (node VARCHAR(5))
INSERT INTO @nodes 
VALUES
('A'),
('B'),
('C'),
('D')

以及包含这些节点如何连接在一起的表格:

DECLARE @connected_nodes TABLE (node_1 VARCHAR(5),node_2 VARCHAR(5))
INSERT INTO @connected_nodes 
VALUES
('A','B'),
('B','C'),
('A','C'),
('C','D')

我需要在 oracle 中编写一个程序来查找两个节点之间的路径,这意味着例如,如果我想从 A 到 C ,在上述情况下我有两条路径: A-->C A--B-->C 所以程序必须返回这两条路径以及跳数(在这种情况下,第一条路径为 1,第二条路径为 2)

注意到存在成百上千个节点。

非常感谢您的帮助

【问题讨论】:

那不是有效的 PL/SQL 或 SQL 【参考方案1】:

您需要一个分层查询来获得所需的结果,应用一些逻辑来仅获取从给定值到另一个值的路径:

with nodes (node) as (
    select 'A' from dual union all
    select 'B' from dual union all
    select 'C' from dual union all
    select 'D' from dual
),
connected_nodes (node_1,node_2 ) as
(
    select 'A','B' from dual union all
    select 'B','C' from dual union all
    select 'B','D' from dual union all
    select 'A','C' from dual union all
    select 'A','D' from dual union all
    select 'D','C' from dual union all
    select 'C','D' from dual
)
select ltrim(sys_connect_by_path(node_1, '->'), '->') as path,
       level -1 as hops
from
    (
    select node as node_1, node_2
    from nodes
           left join connected_nodes
             on(node = node_1)
    )         
where node_1 = 'C'               /* where to stop */
connect by nocycle prior node_2 = node_1
               and prior node_1 is not null
start with node_1 = 'A'         /* where to start from */;

给予:

PATH             HOPS
---------- ----------
A->B->C             2
A->B->D->C          3
A->C                1
A->D->C             2

【讨论】:

当我将 ('A','D') 和 ('D','C') 添加到 connected_nodes 表时,出现以下错误:ORA-01436: CONNECT BY loop in user data 尝试通过 NOCYCLE 连接(尽管您应该检查您的查询和数据是否有循环引用) @tbone 当我添加 NOCYCLE 时,不再检索路径 A->B->C->D (如果最后一个节点是 D ) 你说得对,周期有问题。目前无法检查,我会尽快修复它 我刚刚意识到,如果 connected_nodes 仅包含 2 条记录,比如说 (A,B) 和 (B,C) ,并且我想要从 A 到 C 的路径,则查询不会检索任何路径,你能帮忙吗?【参考方案2】:

请注意,您不能像在 SQL SERVER 中那样在 Oracle 中使用 DECLARE @connected_nodes TABLE。您需要创建一个表并将记录插入到表中。

您需要显示的输出可以通过这个分层查询来实现。

select LTRIM ( SYS_CONNECT_BY_PATH ( node_1,'->' ) ,'->')as paths 
, LEVEL-1 as number_of_hops
FROM connected_nodes WHERE LEVEL > 1 
CONNECT  BY NOCYCLE PRIOR node_2 = node_1
;

这是所有步骤的完整演示。

DEMO

编辑: :如果您只需要查找特定节点(A->C)之间的路径,请使用 附加条件。

select LTRIM ( SYS_CONNECT_BY_PATH ( node_1,'->' ) ,'->')as paths 
, LEVEL-1 as number_of_hops
FROM connected_nodes WHERE
node_1 = 'C' 
START WITH node_1 = 'A' 
CONNECT  BY NOCYCLE PRIOR node_2 = node_1
;

【讨论】:

感谢您的回答,看来SYS_CONNECT_BY_PATH 很有用。如何将结果限制为node1=A 和node2=C 的路径? @user123 :请阅读:- ***.com/help/someone-answers 问题说需要找到A和C之间的路径;这甚至给出了,例如,B->C @Aleksej :“意思是,例如,如果我想从 A 转到 C” - 据我了解,这是众多例子中的一个。 @Aleksej 是对的,我想从节点 A 到节点 C【参考方案3】:

这可能是一种无需分层查询的方法。 他们让我头疼。

drop table connected_nodes;

create table connected_nodes (node_1 VARCHAR2(5),node_2 VARCHAR2(5));

INSERT INTO connected_nodes VALUES('A','B');
INSERT INTO connected_nodes VALUES('B','A');
INSERT INTO connected_nodes VALUES('B','C');
INSERT INTO connected_nodes VALUES('A','C');
INSERT INTO connected_nodes VALUES('C','D');

commit;

drop table links;

create table links 
(from_node_1 VARCHAR2(5),
 from_node_2 VARCHAR2(5),
 to_node_1 VARCHAR2(5),
 to_node_2 VARCHAR2(5),
 first_node_1 VARCHAR2(5),
 first_node_2 VARCHAR2(5),
 link_num NUMBER);

drop table paths;

create table paths as select * from links;

create or replace procedure get_paths(p_node_1 VARCHAR2,p_node_2 VARCHAR2)
as
prev_num_links number;
num_links number;
begin

-- get first links in paths

insert into links 
select 
NULL,
NULL,
cn.node_1,
cn.node_2,
cn.node_1,
cn.node_2,
1
from 
connected_nodes cn
where 
cn.node_1 = p_node_1;

-- loop until number of path links does not increase

prev_num_links := 0;

loop

select count(*) into num_links from links;

if num_links = prev_num_links then
    exit;
end if;

-- add new links

insert into links 
select 
l.to_node_1,
l.to_node_2,
c.node_1,
c.node_2,
l.first_node_1,
l.first_node_2,
l.link_num+1
from connected_nodes c,links l
where 
l.to_node_2 = c.node_1 and
l.to_node_2 <> p_node_2 and
(l.to_node_1,
l.to_node_2,
c.node_1,
c.node_2) not in
(select 
from_node_1,
from_node_2,
to_node_1,
to_node_2
from links);

commit;

prev_num_links := num_links;

end loop;

-- populate paths table with links that go backward
-- from end node to beginning.

-- add end nodes

insert into paths
select * from links
where to_node_2 = p_node_2;

-- loop until number of paths rows does not increase

prev_num_links := 0;

loop

select count(*) into num_links from paths;

if num_links = prev_num_links then
    exit;
end if;

-- add new links

insert into paths 
select 
*
from links l
where 
(l.to_node_1,
l.to_node_2,
l.first_node_1,
l.first_node_2,
l.link_num+1)
in
(select
from_node_1,
from_node_2,
first_node_1,
first_node_2,
link_num
from paths) and
(l.from_node_1,
l.from_node_2,
l.to_node_1,
l.to_node_2,
l.first_node_1,
l.first_node_2,
l.link_num)
not in
(select * from paths);

commit;

prev_num_links := num_links;

end loop;

end;
/
show errors

execute get_paths('A','C');

select 
to_node_1,to_node_2,link_num,first_node_1,first_node_2
from paths
order by first_node_1,first_node_2,link_num;

输出如下所示:

TO_NO TO_NO   LINK_NUM FIRST FIRST
----- ----- ---------- ----- -----
A     B              1 A     B
B     C              2 A     B
A     C              1 A     C

first_node_1 和 first_node_2 列 定义路径和 link_num 列 是它在路径中的哪个链接。

它又长又乱。可能是我没有处理的情况。 我想使用分层查询,除非你不能。这是一 尝试在没有它们的情况下使用 SQL 和 PL/SQL。

【讨论】:

感兴趣的程序!只是一个问题,有理由不使用分层查询吗?他们有什么影响吗? 我不知道有什么理由不使用分层查询,如果他们做你想做的事。我的解决方案是更基本的 SQL/关系数据库。可能我的方法适用于没有分层查询的 SQL 数据库。您可以摆脱 PL/SQL 并从我想象的 Python 或 Java 之类的程序中调用 SQL 语句。也许我的方法适用于大量数据?主要是我的想法,因为我通常不使用其他语法。

以上是关于查找两个节点之间路径的 Oracle/过程的主要内容,如果未能解决你的问题,请参考以下文章

在自定义数据结构上查找JavaScript中两个节点之间的路径

红黑树的插入过程

在java中使用gremlin获取两个节点之间的所有路径

答:SQLServer DBA 三十问之一: charvarcharnvarchar之间的区别(包括用途和空间占用);xml类型查找某个节点的数据有哪些方法,哪个效率高;使用存储 过程和使

hdu1272---小希的迷宫

Oracle怎么导出存储过程