簡體   English   中英

如何獲得分層樹中每一層的子節點的總和?

[英]How to get sum of the children nodes on every level in an hierarchical tree?

我有這樣的分層數據表“A”:

create table dictionary_a
(
  id number not null,
  parent_id number,
  c_name varchar2(50),
  constraint pk_dictionary primary key (id),
  constraint fk_dictionary foreign key (parent_id) references dictionary_a (id)
);
id parent_id c_name
1            name1
2  1         name2
3  1         name3
4  3         name4
5  3         name5
6  2         name6
7  6         name7
...

(實際分層數據表有 7 個級別,但可能會發生變化)

表“B”包含我需要總結的數據:

create table numeric_data
(
  dict_id number not null,
  n_sum number,
  constraint fk_numeric_data foreign key (dict_id) references dictionary_a (id)
);
dict_id n_sum
1       36.0
2       20.0
3       16.0
4       10.5
5       5.5
7       20.0
...

請注意,更高級別的節點也有與之相關的總和。

我需要獲取每個級別的所有子節點的總和,並將它們與 n_sum 列中的實際數據進行比較(此列由用戶填充,我的工作是找出所有不一致的地方):

dict_id n_sum actual_sum c_name
1       36.0  36.0       name1
2       20.0  20.0       name2
3       16.0  16.0       name3
4       10.5  10.5       name4
5       5.5   5.5        name5
6             20.0       name6
7       20.0  20.0       name7

我在網上搜索,但我能找到的都是與具體問題密切相關的,沒有通用的解決方案。

測試數據:

insert into dictionary_a
select level,null,dbms_random.string('L',10) from dual
connect by level < 101;

update dictionary_a a set
a.parent_id = 
  case
    when a.id between 6 and 20 then trunc(dbms_random.value(1,6))
    when a.id between 21 and 40 then trunc(dbms_random.value(6,21))
    when a.id > 40 then trunc(dbms_random.value(21,41))
  end
where a.id > 5;

insert into numeric_data
select level,trunc(dbms_random.value(1,21),2) from dual
connect by level < 101;

commit;

我正在研究 Oracle 18c。

由於您正在生成隨機數據,因此尚不清楚您的預期 output 是什么; 但是,要解決問題:

我需要得到每個級別的所有子節點的總和

您可以生成所有子節點並使用CONNECT_BY_ROOT來記錄層次結構的根 id; 然后你可以對這些值求和以獲得總數:

SELECT root_id,
       MAX(c_name),
       SUM(n_sum) AS total
FROM   (
  SELECT CONNECT_BY_ROOT(id) AS root_id,
         CONNECT_BY_ROOT(c_name) AS c_name,
         n.n_sum
  FROM   dictionary_a d
         INNER JOIN numeric_data n
         ON (d.id = n.dict_id)
  CONNECT BY PRIOR d.id = d.parent_id
)
GROUP BY root_id
ORDER BY root_id

db<> 在這里擺弄


您想要的是對所有葉節點求和:

SELECT root_id,
       MAX(c_name) AS c_name,
       MAX(root_sum) As n_sum,
       SUM(n_sum) AS total
FROM   (
  SELECT CONNECT_BY_ROOT id AS root_id,
         CONNECT_BY_ROOT c_name AS c_name,
         CONNECT_BY_ROOT n_sum AS root_sum,
         d.id,
         n.n_sum
  FROM   dictionary_a d
         LEFT OUTER JOIN numeric_data n
         ON (d.id = n.dict_id)
  WHERE  CONNECT_BY_ISLEAF = 1
  CONNECT BY PRIOR d.id = d.parent_id
)
GROUP BY root_id
ORDER BY root_id

其中,對於您的(非隨機)樣本數據,輸出:

ROOT_ID C_NAME N_SUM 全部的
1 名稱1 36 36
2 名稱2 20 20
3 名稱3 16 16
4 名稱4 10.5 10.5
5 名稱5 5.5 5.5
6 名稱6 null 20
7 名稱7 20 20

db<> 在這里擺弄

您可以在表之間使用外連接:

select da.id, da.parent_id, da.c_name, coalesce(nd.n_sum, 0) as n_sum
from dictionary_a da
left join numeric_data nd on nd.dict_id = da.id;

然后將其用作分層查詢的源,跟蹤根 ID、名稱和數量:

select id,
  parent_id,
  n_sum,
  connect_by_root id as root_id,
  connect_by_root n_sum as root_n_sum,
  connect_by_root c_name as root_c_name,
  connect_by_isleaf as isleaf
from (
  select da.id, da.parent_id, da.c_name, coalesce(nd.n_sum, 0) as n_sum
  from dictionary_a da
  left join numeric_data nd on nd.dict_id = da.id
)
connect by parent_id = prior id;

然后對葉節點求和以獲得您似乎想要的值:

with cte as (
  select id,
    parent_id,
    n_sum,
    connect_by_root id as root_id,
    connect_by_root n_sum as root_n_sum,
    connect_by_root c_name as root_c_name,
    connect_by_isleaf as isleaf
  from (
    select da.id, da.parent_id, da.c_name, coalesce(nd.n_sum, 0) as n_sum
    from dictionary_a da
    left join numeric_data nd on nd.dict_id = da.id
  )
  connect by parent_id = prior id
)
select root_id as dict_id,
  root_n_sum as n_sum,
  sum(n_sum) as actual_sum,
  root_c_name as c_name
from cte
where isleaf = 1
group by root_id, root_n_sum, root_c_name
order by root_id;

哪個與您的明確樣本數據給出:

DICT_ID N_SUM ACTUAL_SUM C_NAME
1 36 36 名稱1
2 20 20 名稱2
3 16 16 名稱3
4 10.5 10.5 名稱4
5 5.5 5.5 名稱5
6 0 20 名稱6
7 20 20 名稱7

我已經包含了coalesce(nv.n_sum, 0) ,因此 ID 6 的“原始” n_sum值顯示為零,而不是您的示例中沒有的 null; 如果您只是刪除合並,它將顯示 null,但包含它意味着您可以添加一個簡單的

having root_n_sum != sum(n_sum)

條款只看到差異。 如果您不理會空值,該子句會變得更加復雜,但它可能更可取:

with cte as (
  select id,
    parent_id,
    n_sum,
    connect_by_root id as root_id,
    connect_by_root n_sum as root_n_sum,
    connect_by_root c_name as root_c_name,
    connect_by_isleaf as isleaf
  from (
    select da.id, da.parent_id, da.c_name, nd.n_sum
    from dictionary_a da
    left join numeric_data nd on nd.dict_id = da.id
  )
  connect by parent_id = prior id
)
select root_id as dict_id,
  root_n_sum as n_sum,
  sum(n_sum) as actual_sum,
  root_c_name as c_name
from cte
where isleaf = 1
group by root_id, root_n_sum, root_c_name
having (root_n_sum is null and sum(n_sum) is not null)
or (root_n_sum is not null and sum(n_sum) is null)
or root_n_sum != sum(n_sum)
order by root_id;

只給出:

DICT_ID N_SUM ACTUAL_SUM C_NAME
6 null 20 名稱6

db<>小提琴

我需要獲取每個級別的所有子節點的總和,並將它們與 n_sum 列中的實際數據進行比較

因此,首先將外部連接到您的數字表兩次,一次用於id ,一次用於parent_id

所有子節點的總和就像對parent_id的分析SUM一樣簡單。

比簡單的 select 所有行child_sum節點 sum不匹配。

WITH dt AS (
select da.id, da.parent_id, da.c_name, 
sum(nd.n_sum) OVER (partition by da.parent_id) as child_sum, 
ndp.n_sum as id_sum
from dictionary_a da
left join numeric_data nd on nd.dict_id = da.id
left join numeric_data ndp on ndp.dict_id = da.parent_id
WHERE parent_id IS NOT NULL)
SELECT * FROM dt
WHERE nvl(child_sum,0) != nvl(id_sum,0)

正如預期的那樣,您會遇到兩個問題

  • 對於父2子總和null但節點總和為 20 並且
  • 對於父6子總和20 ,但節點總和為null

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM