Oracle分层总和(从叶到根的距离)

时间:2018-12-08 14:40:35

标签: sql oracle plsql tree

我想获得有关分层查询(Oracle 11gR2)的帮助。这些查询让我很难受...

实际上,这是2合1问题(需要2种不同的方法)。

我正在寻找一种方法来获取所有个人记录到根的距离(不是相反的)。我的数据位于树状结构中:

CREATE TABLE MY_TREE
(ID_NAME VARCHAR2(1) PRIMARY KEY,
 PARENT_ID VARCHAR2(1),
 PARENT_DISTANCE NUMBER(2)
);

INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('A',NULL,NULL);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('B','A',1);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('C','B',3);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('D','B',5);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('E','C',7);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('F','D',11);
INSERT INTO MY_TREE (ID_NAME,PARENT_ID,PARENT_DISTANCE) VALUES('G','D',13);

从层次上讲,我的数据如下所示(但是我有多个独立的根,并且还有更多的层次):

enter image description here

在第一个方法中,我正在寻找一个查询,该查询将给出以下结果:

LEVEL ROOT NODE ID_NAME ROOT_DISTANCE
----- ---- ---- ------- -------------
1     A    null A       null
2     A    null B       1
3     A    B    C       4
4     A    B    E       11
3     A    B    D       6
4     A    D    F       17
4     A    D    G       19

在此结果中,

  • “ NODE”列表示最接近的拆分元素的ID_NAME
  • “ ROOT_DISTANCE”列表示从元素到根的距离(例如:ID_NAME = G的ROOT_DISTANCE是G到A的距离:G(13)+ D(5)+ B(1)= 19 )

在此方法中,我将始终指定最多2个根。

第二个方法必须是PL / SQL脚本,该脚本将执行相同的计算(ROOT_DISTANCE),但以迭代方式进行,并将结果写入新表中。我想一次运行此脚本,因此将处理所有根(〜1000)。

这是我看脚本的方式:

  • 对于所有根,我们需要找到关联的叶子,然后计算从叶子到根的距离(对于叶子和根之间的所有元素),并将其放入表格中。

“性能角度”需要此脚本,因此,如果已经计算出一个元素(例如:由另一片叶子计算出的分割节点),则需要停止计算并传递到下一个叶子,因为我们已经知道从那里到根的结果。例如,如果系统先计算E-C-B-A,然后计算F-D-B-A,则不应再次计算B-A部分,因为它是在第一遍中完成的。

您可以对这两个问题中的一个或两个都加篷,但我需要为这两个问题加篷。

谢谢!

3 个答案:

答案 0 :(得分:3)

尝试这个:

WITH brumba(le_vel,root,node,id_name,root_distance) AS (
  SELECT 1 as le_vel, id_name as root, null as node, id_name, to_number(null) as root_distance  
  FROM MY_TREE WHERE parent_id IS NULL
  UNION ALL
  SELECT b.le_vel + 1, b.root, 
         CASE WHEN 1 < (
                SELECT count(*) FROM MY_TREE t1 WHERE t1.parent_id = t.parent_id
              )
              THEN t.parent_id ELSE b.node
         END,
         t.id_name, coalesce(b.root_distance,0)+t.parent_distance
  FROM MY_TREE t
  JOIN brumba b ON b.id_name = t.parent_id
)
SELECT * FROM brumba

演示:https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=d5c231055e989c3cbcd763f4b3d3033f


不需要使用PL / SQL进行“第二次处理”-上面的SQL将立即计算所有根节点(在parent_id列中为空)的结果。
只需在上述查询中添加INSERT INTO tablename(col1,col2, ... colN) ...CREATE TABLE name AS ...前缀即可。
上面的演示包含后一个选项CREATE TABLE xxx AS query

的示例

答案 1 :(得分:1)

以下是一个显示如何获得问题第一部分的选项:

SQL> with temp as
  2    (select level lvl,
  3            ltrim(sys_connect_by_path(id_name, ','), ',') path
  4     from my_tree
  5     start with parent_id is null
  6     connect by prior id_name = parent_id
  7    ),
  8  inter as
  9    (select t.lvl,
 10            t.path,
 11            regexp_substr(t.path, '[^,]+', 1, column_value) col
 12     from temp t,
 13          table(cast(multiset(select level from dual
 14                              connect by level <= regexp_count(path, ',') + 1
 15                             ) as sys.odcinumberlist ))
 16    )
 17  select i.lvl,
 18         i.path,
 19         sum(m.parent_distance) dis
 20  from inter i join my_tree m on m.id_name = i.col
 21  group by i.lvl, i.path
 22  order by i.path;

 LVL PATH              DIS
---- ---------- ----------
   1 A
   2 A,B                 1
   3 A,B,C               4
   4 A,B,C,E            11
   3 A,B,D               6
   4 A,B,D,F            17
   4 A,B,D,G            19

7 rows selected.

SQL>

答案 2 :(得分:0)

这是如何通过分层(connect by)查询来解决此问题的方法。

在大多数分层问题中,分层查询将比递归查询(递归with子句)更快。但是,您的问题并非纯粹是分层的-您需要计算到根的距离,并且与递归with不同,对于分层查询,您无法一次通过。因此,听到您的来信将会很有趣! -两种方法之间是否存在明显的性能差异。就其价值而言,在您提供的非常小的数据样本上,优化器使用connect by估算的成本为5,而递归解决方案的成本为48;这是否意味着您将发现现实生活中的任何东西,并希望您也能让我们知道。

在分层查询中,我标记出有两个或更多孩子的父母(我为此使用了解析函数,以避免联接)。然后,我构建层次结构,并在最后一步中进行汇总以获取所需的位。与递归解决方案一样,这应该在单个SQL查询中为您提供所有所需的一切-对于所有根和所有节点;不需要PL / SQL代码。

with
  branching (id_name, parent_id, parent_distance, b) as (
    select id_name, parent_id, parent_distance, 
           case when count(*) over (partition by parent_id) > 1 then 'y' end
    from   my_tree
  )
, hierarchy (lvl, leaf, id_name, parent_id, parent_distance, b) as (
    select  level, connect_by_root id_name, id_name, parent_id, parent_distance, b
    from    branching
    connect by id_name = prior parent_id
  )
select   max(lvl) as lvl, 
         min(id_name) keep (dense_rank last order by lvl) as root,
         leaf as id_name,
         min(decode(b, 'y', parent_id)) 
           keep (dense_rank first order by decode(b, 'y', lvl)) as node,
         sum(parent_distance) as root_distance
from     hierarchy
group by leaf;

LVL ROOT    ID_NAME NODE    ROOT_DISTANCE
--- ------- ------- ------- -------------
  1 A       A                            
  2 A       B                           1
  3 A       C       B                   4
  3 A       D       B                   6
  4 A       E       B                  11
  4 A       F       D                  17
  4 A       G       D                  19