【发布时间】:2017-12-01 14:58:07
【问题描述】:
说明
在我们的问题域中,我们正在研究一组连接在一起形成图的边。 从给定节点(或多个节点)开始,我们必须列出整个图中连接到给定节点(或多个节点)的所有链接。 我们必须从左到右、从上到下显示这些链接。
对于具有有限循环数的图,我们有一个针对此问题的有效查询。循环次数越多,执行时间就越多。
我们需要在递归期间限制对同一节点的访问以获得有效的解决方案。
下面的示例只包含一个循环,但是这个循环已经导致了 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;
【问题讨论】:
-
您的预期结果排序对我来说没有意义,对不起。能否请您逐步描述关于预期结果排序的图遍历?
-
这个想法是将具有相同父项的项目组合在一起。如果没有找到共同的父项,则如果项目具有共同的子项,则对它们进行分组。在所有其他情况下,使用自然排序。我引入了 LVL 的概念来指示到起点的水平距离。具有共同父节点或共同子节点的节点应该具有相同的深度。如果下一个项目使用其 TO_ID 列加入,我会降低 LVL。如果下一个项目使用其 FROM_ID 列加入,我会增加 LVL。在所有其他情况下,LVL 保持不变。
-
但排序本身并不重要。我正在寻找一种方法来限制递归期间路径的爆炸。我相信stackoverflow.com/questions/8764701/… 的解决方案是可行的方法,但我需要一个 Oracle 等价物。我尝试使用路径的字符串表示,但后来我得到 字符串连接的结果太长.
-
“水平距离”的概念是我无法理解的。 “C”与“A”的距离与“B”与“A”的距离有何不同?不过,如果排序不是那么重要,那么我将把它放在一边,不再考虑。
-
距离确实不是正确的术语。水平位置也许更合适?
C --> A --> BC --> E --> B0 --> 1 --> 2每个节点都有一个 FROM_ID 和一个 TO_ID。如果您使用current.FROM_ID = next.TO_ID返回,则新的 LVL 是旧的 LVL - 1。如果您愿意,可以完全忽略排序,因为这可能是对其他人有帮助的具体方式,并且似乎可以正确处理来自的查询这个问题。我期待有关性能的任何建议!
标签: sql oracle11g common-table-expression recursive-query