【问题标题】:Oracle hierarchical query on non-hierarchical dataOracle对非分层数据的分层查询
【发布时间】:2011-11-30 11:34:02
【问题描述】:

我有一个 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

我的目标是获取节点 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;

这样一个查询的问题是它会遍历所有可能的路径,直到它们循环,并且在我的实际数据中它们太多了。结果包含许多重复项 - 这是:

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

我的实际数据要复杂得多。执行此类查询的成本非常高,以至于我的可自动扩展的 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

我遇到的问题是:

  • 在 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 子句,我会得到循环(ORA-32044: cycle detected while executing recursive WITH query)。

您将如何查询我的原始数据以有效地获取节点 3、4、5、6、8?也欢迎 PL/SQL 解决方案。

谢谢。

【问题讨论】:

    标签: sql oracle data-structures plsql


    【解决方案1】:

    在访问超过 4000 字节之前,这可能会有所帮助。循环应该是不可能的,但这条线只是作为一个例子。

       WITH descendants(node, lvl, pth, visited) AS
        (
        SELECT child node, 1, cast(child as varchar2(4000)), '/'||listagg(child,'/') within group (order by child) over()||'/'
          FROM t 
         where parent = 2
         UNION ALL
        SELECT child, lvl+1, pth||'/'||child, D.visited||listagg(child,'/') within group (order by child) over()||'/'
          FROM T
         INNER JOIN descendants D
            ON T.parent = D.node
         WHERE D.visited not like '%/'||child||'/%'
        )
        cycle node set cyc to '1' default '0' 
        SELECT distinct node
          FROM descendants
         order by node
        ;
    

    【讨论】:

      【解决方案2】:

      我自己没有使用过这个,但是带有 NOCYCLE 选项的 CONNECT BY 怎么样?当它看到一个循环时,它应该停止遍历树。 Oracle 11i 肯定有,我认为它是在 Oracle 10g 时期的某个地方出现的。

      【讨论】:

      • connect by 就像一个魅力。请参阅此处的示例:adp-gmbh.ch/ora/sql/connect_by_nocycle.html
      • 感谢您的回答,但是 -1:重新阅读我的问题,我目前正在使用 CONNECT BY,但它在该类型的数据上表现不佳。
      【解决方案3】:

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

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

      (注意,我不是 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;
      

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

      编辑

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

      GROUP BY 或许多其他选项可用于解决此问题,而不是 DISTINCT。只是它自己的 NOT EXISTS 是不够的。

      【讨论】:

      • 这看起来不错,谢谢。这是拥有全局临时表的情况吗?
      • 我不希望表是全局的。想象一下如果两个进程开始一起使用它会发生什么混乱? (如果源表在执行过程中被修改,它已经对“异常”行为开放。但如果需要,您可以在事务中保护整个事物。)
      • @Dems:只是关于全局临时表的说明,正如您所说的,您不是 Oracle 专家。在 Oracle 中,情况有所不同:What is the difference between a temporary table vs global temporary table in Oracle? 和 Creating a Temporary Table
      • 那么对 Benoit 问题的回答就是“是”:)
      • @Dems:我已经实现了一个函数,它按照您的回答精神返回TABLE OF OBJECT。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多