【问题标题】:How to get sum of the children nodes on every level in an hierarchical tree?如何获得分层树中每一层的子节点的总和?
【发布时间】:2022-01-04 14:22:17
【问题描述】:

我的表“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 (ID, PARENT_ID, C_NAME)
values (1, null, 'Department 1');

insert into dictionary_a (ID, PARENT_ID, C_NAME)
values (2, 1, 'Department 2');

insert into dictionary_a (ID, PARENT_ID, C_NAME)
values (3, 1, 'Department 3');

insert into dictionary_a (ID, PARENT_ID, C_NAME)
values (4, 3, 'Department 4');

insert into dictionary_a (ID, PARENT_ID, C_NAME)
values (5, 3, 'Department 5');

insert into dictionary_a (ID, PARENT_ID, C_NAME)
values (6, 2, 'Department 6');

insert into dictionary_a (ID, PARENT_ID, C_NAME)
values (7, 6, 'Department 7');

insert into numeric_data (DICT_ID, N_SUM)
values (1, 36);

insert into numeric_data (DICT_ID, N_SUM)
values (2, 20);

insert into numeric_data (DICT_ID, N_SUM)
values (3, 16);

insert into numeric_data (DICT_ID, N_SUM)
values (4, 10.5);

insert into numeric_data (DICT_ID, N_SUM)
values (5, 5.5);

insert into numeric_data (DICT_ID, N_SUM)
values (7, 20);

commit;

我正在开发 Oracle 18c。

【问题讨论】:

  • 您最好生成非随机数据并包含您的预期输出。您还说您想“将它们与 n_sum 列中的实际数据进行比较”,但是,没有其他列可以包含数字数据。
  • 你是对的。我现在编辑可以吗?

标签: sql oracle oracle18c


【解决方案1】:

您可以在表之间使用外连接:

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 name1
2 20 20 name2
3 16 16 name3
4 10.5 10.5 name4
5 5.5 5.5 name5
6 0 20 name6
7 20 20 name7

我已包含coalesce(nv.n_sum, 0),因此 ID 6 的“原始”n_sum 值显示为零而不是您的示例中没有的空值;如果您只是删除合并,它将显示 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 name6

db<>fiddle

【讨论】:

  • 这正是我需要的解决方案。现在很明显,我并没有完全理解我最初的目标,对这件事的认识不足。我编辑了原始帖子并用非随机数据替换了测试数据。
【解决方案2】:

由于您正在生成随机数据,因此不清楚您的预期输出是什么;但是,要解决问题:

我需要得到每一层所有子节点的总和

您可以生成所有子节点,并使用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小提琴here


您似乎想要的不是对所有子节点求和,而是对所有叶节点求和:

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 TOTAL
1 name1 36 36
2 name2 20 20
3 name3 16 16
4 name4 10.5 10.5
5 name5 5.5 5.5
6 name6 null 20
7 name7 20 20

db小提琴here

【讨论】:

    【解决方案3】:

    我需要获取每个级别的所有子节点的总和,并将它们与 n_sum 列中的实际数据进行比较

    如果您只将每个父级与其子级的总和进行比较,则不需要使用分层查询

    因此,首先将外部连接到您的 number 表两次,一次用于id,一次用于parent_id

    所有子节点的总和就像解析 SUMparent_id 一样简单。

    选择child_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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-10
      • 1970-01-01
      • 2021-12-24
      • 1970-01-01
      • 1970-01-01
      • 2020-01-16
      • 2021-09-19
      • 2017-06-02
      相关资源
      最近更新 更多