在特定深度使用group by的CTE递归

时间:2018-05-02 13:57:56

标签: sql-server common-table-expression

我有一个代表文件夹结构的表 - (即id和父字段的递归) - 这个数据实际上代表文件夹结构中的项目(项目字段只是示例,但我的情况是一个连接到很多东西。 我创建了一个路径字符串(不需要使用) - 这个字符串表示数据集中每个记录的路径(分支字段)

我的查询需要按超过3级深度的所有记录进行过滤,然后计算每个递归分支中3级或更低级别的任何项目(在我的数据中,这将意味着记录5和6代表顶级节点,将是用于分组的记录。

任何帮助?

DECLARE @tbl TABLE
  ( 
   Id int
  ,ParentId int
  ,branch varchar(100)
  ,depth int
  ,item varchar(20)
  )
INSERT  INTO @tbl
        ( Id, ParentId,item )
VALUES 
(1, NULL,Null),
(2, 1,Null),
(3, 1,Null),
(4, 3,Null),
(5, 4,Null),
(6, 5,'car'),
(7, 6,'bus'),
(8, 7,'truck'),
(9, 8,'car'),
(10,8,'bike'),
(11,5,'car'),
(12,5,'truck'),
(13,4,'truck'),
(14,8,'bike'),
(15,8,'bus');

--select t_package.package_id, t_package.parent_ID from t_package 

;WITH abcd
        AS (
              -- anchor
            SELECT   id
                    ,ParentID
                    ,CAST(id AS VARCHAR(100)) AS [Path]
                    ,0 as depth
                    ,item
            FROM    @tbl
            WHERE   ParentId is Null
            UNION ALL
              --recursive member
            SELECT  t.id
                   ,t.ParentID
                   ,CAST(a.[Path] + ',' + CAST( t.ID AS VARCHAR(100)) AS varchar(100)) AS [Path]
                   ,a.depth +1
                   ,t.item
            FROM    @tbl AS t
                    JOIN abcd AS a ON t.ParentId = a.id
           )
insert into @tbl (id,parentID,branch,depth,item) select * from abcd

select * from @tbl 
where branch is not null

这意味着如果您在第3级分组,并且需要每个级别的项目计数,那么结果集将如下所示:

ID     Depth  -- car -- bike -- bus -- truck
5      3      --  3  --  2   -- 2   --  2  
13     3      --  0  --  0   -- 0   --  1

2 个答案:

答案 0 :(得分:1)

这非常有趣,所以我不得不推动我以前的CTE经验。

以下是一个应该完成目标的查询。但请注意,在您获得所需结果的方式中,您始终会在查询中对Item值进行硬编码。最有可能的是,Item的值的结果域正在发生变化,您将不得不考虑编写动态SQL。在下面的解决方案中,我使用"条件"聚合,已被证明不是"性能破坏者"。我确定可以使用PIVOT语法引入另一种方法。 性能,应进一步分析,并可能引入一些中间索引方案。

这应该做到这一点,并希望它带给你一些好处:

DECLARE @summaryDepth INT = 3;

DECLARE @tbl TABLE( 
    ID INT, ParentID INT, Item VARCHAR(20));
INSERT @tbl (
    ID, ParentID, Item)
VALUES
    (1,  NULL, NULL),
    (2,  1, NULL),
    (3,  1, NULL),
    (4,  3, NULL),
    (5,  4, NULL),
    (6,  5, 'car'),
    (7,  6, 'bus'),
    (8,  7, 'truck'),
    (9,  8, 'car'),
    (10, 8, 'bike'),
    (11, 5, 'car'),
    (12, 5, 'truck'),
    (13, 4, 'truck'),
    (14, 8, 'bike'),
    (15, 8, 'bus');

/*
(1, NULL, NULL),*/
--(2, 1, NULL),
--(3, 1, NULL),
----(4, 3, NULL),
------(13,4,'truck'),
------(5, 4, NULL),
--------(6, 5,'car'),
--------(11,5,'car'),
--------(12,5,'truck'),
----------(7, 6,'bus'),
------------(8, 7,'truck'),
--------------(9, 8,'car'),
--------------(10,8,'bike'),
--------------(14,8,'bike'),
--------------(15,8,'bus');

;WITH CTE_Hierarchy AS (
    SELECT 
        ID, ParentID, Item,
        CAST(ID AS VARCHAR(100)) AS [Path],
        0 [Depth],
        CASE 
            WHEN @summaryDepth = 0 THEN ID 
            ELSE NULL 
        END [SummaryDepthRootID]
    FROM @tbl
    WHERE ParentId IS NULL

    UNION ALL

    SELECT
        child.ID, child.ParentID, child.Item,
        CAST(parent.[Path] + '/' + CAST(child.ID AS VARCHAR(100)) AS VARCHAR(100)) [Path],
        parent.[Depth] + 1 [Depth],
        CASE 
            WHEN parent.SummaryDepthRootID IS NOT NULL THEN parent.SummaryDepthRootID
            WHEN @summaryDepth = (parent.[Depth] + 1) THEN child.ID 
            ELSE NULL 
        END [SummaryDepthRootID]
    FROM @tbl AS child
    JOIN CTE_Hierarchy AS parent ON parent.ID = child.ParentID
)
SELECT 
    SummaryDepthRootID [ID],
    @summaryDepth [Depth],
    COUNT (CASE WHEN Item='car' THEN 1 ELSE NULL END) [car],
    COUNT (CASE WHEN Item='bike' THEN 1 ELSE NULL END) [bike],
    COUNT (CASE WHEN Item='bus' THEN 1 ELSE NULL END) [bus],
    COUNT (CASE WHEN Item='truck' THEN 1 ELSE NULL END) [truck]
FROM CTE_Hierarchy
WHERE SummaryDepthRootID IS NOT NULL
GROUP BY SummaryDepthRootID;
GO

答案 1 :(得分:0)

您也可以在第3级启动递归CTE 然后转动物品。

示例代码:

DECLARE @tbl TABLE(ID INT, ParentID INT, Item VARCHAR(20));
INSERT INTO @tbl (ID, ParentID, Item) VALUES
                            (1,  NULL, NULL),
                        (2,  1, NULL),
                        (3,  1, NULL),
                    (4,  3, NULL),
                (5,  4, NULL),
            (6,  5, 'car'),
        (7,  6, 'bus'),
    (8,  7, 'truck'),
(9,  8, 'car'),
(10, 8, 'bike'),
            (11, 5, 'car'),
            (12, 5, 'truck'),
                 (13, 4, 'truck'),
(14, 8, 'bike'),
(15, 8, 'bus');

;with CTE as (
    select t0.ID as tier0id, t1.ID as tier1id, t2.ID as tier2id, t3.ID as tier3id, 0 as lvl, t3.ID, t3.ParentID, t3.Item
    from @tbl t0
    join @tbl t1 on (t0.ParentID is null and t1.ParentID = t0.ID)
    join @tbl t2 on (t2.ParentID = t1.ID)
    join @tbl t3 on (t3.ParentID = t2.ID)

    union all

    select tier0id, tier1id, tier2id, tier3id, lvl + 1, t.ID, t.ParentID, t.Item
    from CTE
    join @tbl t on (t.ParentID = CTE.ID)
)
select *
from (
    select distinct tier3id as ID, 3 as Depth, ID as ChildId, Item 
    from CTE
) q
pivot (
    count(ChildId) 
    for Item in ([car], [bike], [bus], [truck])
) pvt
order by ID;