Oracle分层查询非分层数据

时间:2011-10-04 07:57:54

标签: sql oracle data-structures plsql

我在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

Data sample

我的目标是获得节点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解决方案也很受欢迎。

谢谢。

3 个答案:

答案 0 :(得分: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;

根据效果,您可能还需要depth中的myMap字段;优化连接以便仅加入最近的节点。这意味着两个指标;一个用于JOIN (depth),另一个用于NOT EXISTS (parent)

修改

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

GROUP BY或许多其他选项可用于满足此要求,而不是DISTINCT。只是它本身的NOT EXISTS是不够的。

答案 1 :(得分:1)

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

答案 2 :(得分: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
    ;