简体   繁体   中英

How to count data in tree-like structure in SQL, for every node

I have a tree like this: 在此处输入图片说明

I have this tree hierarchy in table. One row for every node. I want to display count of children for every node in the tree. Result would be:

ID COUNT
100 9
129 5
439 3
450 1
501 2
602 1
134 3
133 2

Table schema:

Table - Organization_structure
orgID
parentID

Table - Organization_detail
RID (PK)
OrganizationID
ParentOrganizationID

data:
orgID parrent ID
602   501
501   439
450   129
439   129
129   100
133   134
134   100

RID OrganizationID ParentOrganizationID
1   100            top
2   129            100
3   439            129
4   450            129
5   501            439
6   602            501
7   134            100
8   133            134
9   133            134

Any help how to achieve this? Best in SQL server.

It is a bit complicated because you should use a recursive query and then I've used a CROSS APPLY join to count records:

I've set up next example, (before you post your data)

CREATE TABLE Organization_structure (orgID int, parentID int);

INSERT INTO Organization_structure VALUES
(100, NULL),
(129, 100),
(134, 100),
(439, 129),
(450, 129),
(133, 134),
(133, 134),
(501, 439),
(602, 501);

This is my solution:

with tree as
(
    select orgId, parentId,0 as tree_order, path = cast('root' as varchar(100)) 
    from   Organization_structure 
    where  parentID is null
    union all
    select os.orgId, os.parentId, 1 + tree_order as tree_order,
           path = cast(tree.path + '/' + right(('000000000' + os.orgId), 10) as varchar(100))
    from   Organization_structure os
    join   tree 
    on     tree.orgId = os.parentId
)
select orgId, tree_order, path, t2.cnt 
from tree
cross apply (select count(*) cnt from tree t1 where t1.path like tree.path + '%') t2
order by tree_order;

And this is the result:

\norgId |  tree_order |  path |  cnt \n----: |  ---------: |  :------------------- |  --: \n  100 |  0 |  root |  9 \n  129 |  1 |  root/129 |  5 \n  134 |  1 |  root/134 |  3 \n  133 |  2 |  root/134/133 |  2 \n  133 |  2 |  root/134/133 |  2 \n  439 |  2 |  root/129/439 |  3 \n  450 |  2 |  root/129/450 |  1 \n  501 |  3 |  root/129/439/501 |  2 \n  602 |  4 |  root/129/439/501/602 |  1 \n

dbfiddle here

If you convert your data to hierarchyid , you could use the IsDescendantOf method to get those. There may be a simpler way, but this was my first thought. The first part is just formatting the data for hierarchyid.

The count magic comes where I find all nodes which are descendents of the current node. By grouping on the orgid you get the count of all descendents.

with data (OrgId, ParentOrgId) as
(
    select 100, null union all
    select 129, 100 union all
    select 134, 100 union all
    select 133, 134 union all
    select 135, 134 union all
    select 439, 129 union all
    select 450, 129 union all
    select 501, 439 union all
    select 602, 501 
), cte as
(
    select 
        sPath = cast(concat('/', OrgId, '/') as varchar(max)),
        Path = hierarchyid::Parse(concat('/', OrgId, '/')),
        PreviousPath = hierarchyid::GetRoot(),
        OrgId,
        ParentOrgId
    from data
    where ParentOrgId is null
    union all
    select 
        sPath = cast(concat(c.sPath, s.OrgId, '/') as varchar(max)),
        Path = hierarchyid::Parse(concat(c.sPath, s.OrgId, '/')),
        PreviousPath = c.Path,
        OrgId = s.OrgId,
        ParentOrgId = s.ParentOrgId
    from cte c
    inner join data s
        on c.OrgId = s.ParentOrgId
)
select a.OrgId, NumChildren = count(1)
from cte a
inner join cte b
    on b.Path.IsDescendantOf(a.Path) = 1
group by a.OrgId

You might even be able to just keep a running total inside the CTE checking for descendents while simultaneously constructing the hierarchyid path.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM