Oracle分层查询非分层数据

Ben*_*oit 14 sql oracle plsql data-structures

我在Oracle表中提供数据,该表被组织为可以包含循环的图(参见示例).

     CREATE TABLE T (parent INTEGER, child INTEGER)
               AS select 1 parent, 2 child from dual
        union all select 1 parent, 8 child from dual
        union all select 2 parent, 3 child from dual
        union all select 2 parent, 4 child from dual
        union all select 2 parent, 8 child from dual
        union all select 3 parent, 4 child from dual
        union all select 3 parent, 6 child from dual
        union all select 4 parent, 5 child from dual
        union all select 5 parent, 8 child from dual
        union all select 6 parent, 5 child from dual
        union all select 7 parent, 3 child from dual
        union all select 7 parent, 5 child from dual
        union all select 8 parent, 6 child from dual
Run Code Online (Sandbox Code Playgroud)

数据样本

我的目标是获得节点X的后代(孩子,孩子的孩子等)的所有节点.假设2.我的预期结果是:3,4,5,6,8.

我知道我可以设计一个这样的查询:

SELECT child, sys_connect_by_path(child,'/')
   FROM T
  START WITH parent = 2
CONNECT BY NOCYCLE PRIOR child = PARENT;
Run Code Online (Sandbox Code Playgroud)

这种查询的问题在于它将遍历所有可能的路径,直到它们循环,并且在我的实际数据中有太多它们.结果包含许多重复 - 这是:

child | sys_connect_by_path (for information)
3     | /3
4     | /3/4
5     | /3/4/5
8     | /3/4/5/8
6     | /3/4/5/8/6
6     | /3/6
5     | /3/6/5
8     | /3/6/5/8
4     | /4
5     | /4/5
8     | /4/5/8
6     | /4/5/8/6
8     | /8
6     | /8/6
5     | /8/6/5
Run Code Online (Sandbox Code Playgroud)

我的实际数据要复杂得多.执行此类查询的成本非常高,以至于我的TEMP表空间(可自动扩展)达到了10Gb(最初为500 Mb),而且由于磁盘已满,我的数据库实际上已损坏.

我尝试像这样设计查询(递归WITH子句):

WITH descendants(node) AS
( SELECT 2 node FROM dual
  UNION ALL
  (
  SELECT child
    FROM T
   INNER JOIN descendants D
      ON T.parent = D.node
   MINUS SELECT node FROM descendants
  )
)
SELECT * FROM descendants
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是:

  • 使用Oracle 10g,这没有实现(ORA-32033: unsupported column aliasing有些客户使用Oracle 9或10),
  • 有了Oracle 11g,我明白了ORA-32041: UNION ALL operation in recursive WITH clause must have only two branches.如果我删除MINUS子句,我将得到cycles(ORA-32044: cycle detected while executing recursive WITH query).

您如何查询我的原始数据以有效地获取这些节点3,4,5,6,8?PL/SQL解决方案也是受欢迎的.

谢谢.

Mat*_*lie 2

您期望到达任何子节点的最大深度是多少?

如果它相对较小,您可以循环下来,同时检查您已经访问过的节点,以类似这样的方式......

(注意,我不是 Oracle 专家,所以这更接近于混合了一点真实 SQL 的伪代码)

CREATE TABLE myMap (parent INT, child INT);

INSERT INTO myTable SELECT NULL, 2 FROM DUAL;

WHILE (SQL%ROWCOUNT > 0)
LOOP

  INSERT INTO
    myMap
  SELECT DISTINCT
    dataMap.parent,
    dataMap.child
  FROM
    myMap
  INNER JOIN
    dataMap
      ON myMap.child = dataMap.parent
  WHERE
    NOT EXISTS (SELECT * FROM myMap WHERE parent = dataMap.parent)

END LOOP;
Run Code Online (Sandbox Code Playgroud)

根据性能,您可能还需要一个depth字段myMap;优化连接,以便仅连接最近的节点。这意味着两个索引;一种用于 JOIN (depth),一种用于 NOT EXISTS (parent)

编辑

添加了 DISTINCT 关键字,以避免出现以下情况...
- 节点 2 映射到 3 和 4
- 节点 3 和 4 都映射到节点 5
- 节点 5 的所有子节点现在将被处理两次

GROUP BY 或许多其他选项可以用来代替 DISTINCT 来满足此要求。只是 NOT EXISTS 本身是不够的。