描述
在我们的问题域中,我们正在研究一组连接在一起形成图的边。
从给定的节点(或多个节点)开始,我们必须列出整个图中连接到给定节点(或多个节点)的所有链接。
我们必须从左到右、从上到下显示这些链接。
对于循环数量有限的图,我们有一个针对此问题的有效查询。循环次数越多,执行时间就会呈指数增长。
我们需要在递归期间限制对同一节点的访问以获得有效的解决方案。
下面的示例仅包含一个循环,但该单个循环已导致 86 个额外的过时行。
在类似的帖子中,使用 ROW 和 ANY 运算符为 postgresql 提供了解决方案,但我找不到 Oracle 解决方案。
我们正在寻找解决方案的替代方案或限制对相同边缘的访问次数的方法。
任何帮助是极大的赞赏!
Similar
使用递归查询像访问无向图一样访问有向图 https://stackoverflow.com/questions/8764701/visiting-a-directed-graph-as-if-it-were-an-undirected-one-using-a-recursive-quepostgresql中提供了解决方案。
我们需要使用Oracle11g。
Example
Edges
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');
Input
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;