通过 SQL 中的层次结构进行计算

Calculations through a hierarchy in SQL

我正在尝试通过在层次结构中导航来执行一些计算。在下面的简单示例中,组织有人数并且可以与上级组织相关联,人数仅为 "leafs" 组织指定。我想使用简单的规则计算层次结构中一直向上的人数:parent_headcount = sum(children_headcount)。 我喜欢为此使用 SQL Common Table Expression 的想法,但这并不完全有效。级别的确定有效(因为它遵循自然的自上而下的导航顺序),但人数确定无效。 您将如何解决这个问题,或者是否有更好的自下而上执行此计算的方法?

-- Define the hierachical table Org
drop table if exists Org
create table Org (
    ID int identity (1,1) not null, Name nvarchar(50), parent int null, employees int,
    constraint [PK_Org] primary key clustered (ID),
    constraint [FK_Parent] foreign key (parent) references Org(ID)
);

-- Fill it in with a simple example
insert into Org (name, parent, employees) values ('ACME', NULL, 0);
insert into Org (name, parent, employees) values ('ACME France', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME UK', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME Paris', (select Org.ID from Org where Name = 'ACME France'), 200);
insert into Org (name, parent, employees) values ('ACME Lyons', (select Org.ID from Org where Name = 'ACME France'), 100);
insert into Org (name, parent, employees) values ('ACME London', (select Org.ID from Org where Name = 'ACME UK'), 150);
select * from Org;

-- Try to determine the total number of employees at any level of the hierarchy
with Orgs as (
    select
        ID, name, parent, 0 as employees, 0 as level from Org where parent is NULL
    union all
    select 
        child.ID, child.name, child.parent, Orgs.employees + child.employees, level + 1 from Org child
        join Orgs on child.parent = Orgs.ID
)
select * from Orgs;

这个查询 returns:

层级确定正确,但人数计算不正确(英国应为150,法国应为300,最高层应为450)。好像CTE适合自上而下导航,但不适合自下而上?

您需要遍历每个非叶节点的层次结构,并对来自该节点的所有路径求和。

with Orgs as (
    select
        id as [top], ID, name, parent, 0 as employees, 0 as level 
        from Org g
        where exists (select 1 from Org g2 where g.ID = g2.parent)
    union all
    select 
        orgs.[top], child.ID, child.name, child.parent, Orgs.employees + child.employees, level + 1 from Org child
        join Orgs on child.parent = Orgs.ID
)
select [top] as id, sum(employees) employees
from Orgs
group by [top];

Db fiddle

试试这个: /***** 数据 *************/

-- Define the hierachical table Org
drop table Org
create table Org (
    ID int identity (1,1) not null, Name nvarchar(50), parent int null, employees int,
    constraint [PK_Org] primary key clustered (ID),
    constraint [FK_Parent] foreign key (parent) references Org(ID)
);

-- Fill it in with a simple example
insert into Org (name, parent, employees) values ('ACME', NULL, 0);
insert into Org (name, parent, employees) values ('ACME France', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME UK', (select Org.ID from Org where Name = 'ACME'), 0);
insert into Org (name, parent, employees) values ('ACME Paris', (select Org.ID from Org where Name = 'ACME France'), 200);
insert into Org (name, parent, employees) values ('ACME Lyons', (select Org.ID from Org where Name = 'ACME France'), 100);
insert into Org (name, parent, employees) values ('ACME London', (select Org.ID from Org where Name = 'ACME UK'), 150);
select * from Org;

/******** 结束数据 ***********/

/******** 查询 ******/

-- 尝试确定层次结构中任何级别的员工总数

with Orgs as (
    select
        ID, name, parent,  employees, ID as RootID, 0 as level  from Org 
    union all
    select 
        child.ID , child.name, child.parent, child.employees, Orgs.RootID, level + 1  from Org child
        join Orgs on child.parent = Orgs.ID
)

select Org.Id,
       Org.Parent,
       Org.Name,
       Org.employees,
       (select max(level) from Orgs a where a.Id = Org.Id) as [Level],
       S.ProductCountIncludingChildren
from Org
  inner join (
             select RootID,  
                    sum(employees) as ProductCountIncludingChildren
             from Orgs
             group by RootID
             ) as S
    on Org.Id = S.RootID

left join Org Org2 on Org2.ID = Org.Parent

order by Org.Id

/**** 结束查询 ******/

只是另一个使用数据类型 hierarchyid

的选项

注意:@Top 和嵌套是可选的

示例

Declare @Top int = null

;with cteP as (
      Select ID
            ,Parent 
            ,Name 
            ,HierID = convert(hierarchyid,concat('/',ID,'/'))
            ,employees
      From   Org 
      Where  IsNull(@Top,-1) = case when @Top is null then isnull(Parent ,-1) else ID end
      Union  All
      Select ID  = r.ID
            ,Parent  = r.Parent 
            ,Name   = r.Name
            ,HierID = convert(hierarchyid,concat(p.HierID.ToString(),r.ID,'/'))
            ,r.employees
      From   Org r
      Join   cteP p on r.Parent  = p.ID)
Select Lvl   = A.HierID.GetLevel()
      ,A.ID
      ,A.Parent
      ,Name  = Replicate('|---',A.HierID.GetLevel()-1) + A.Name
      ,Employees = sum(B.Employees)
 From  cteP A
 Join  cteP B on B.HierID.ToString() like A.HierID.ToString()+'%'
 Group By A.ID,A.Parent,A.Name,A.HierID
 Order By A.HierID

Returns