【问题标题】:SQL Server CTE for Recursion and Ordering用于递归和排序的 SQL Server CTE
【发布时间】:2017-06-15 17:09:01
【问题描述】:

我在 SQL Server (2012) 中有下表:

我的表

Id      __ParentId      Priority
1       NULL            NULL    
2       1               100     
3       1               300     
4       1               200     
5       4               100     
6       4               200     
7       6               100     
8       5               100     
9       5               200     
10      9               100     
11      5               50      

__ParentId 列引用 Id 以了解任何一行的父级,它可以向下递归到多个级别(例如,Id 85 的孩子,4 的孩子,1 的孩子)。

此外,还有一个 Priority 列,显示子级在父级中出现的顺序(最小的数字优先)。

所以,我想要的决赛桌是:

Id      __ParentId  Priority    Order   
1       NULL        NULL        1       
2       1           100         2       
4       1           200         3       
5       4           100         4       
11      5           50          5       
8       5           100         6       
9       5           200         7       
10      9           100         8       
6       4           200         9       
7       6           100         10      
3       1           300         11      

为了解释一下,我们有 21 的一个孩子,并且具有最高优先级,但没有孩子,所以我们停在那里,然后 4 是下一个优先级孩子,所以它去了接下来,但我们会根据优先级和层次结构分为它的子代和它们的子代。

或者,通过树结构来解释:

 1
    2
    4
        5
            11
            8
            9
                10
        6
            7
    3

我可以创建将给我父母的孩子的 CTE,但我无法找到获得正确排序的好方法,因此我什至无法提供我一直在尝试的良好 SQL。

【问题讨论】:

    标签: sql-server tsql recursion sql-server-2012 common-table-expression


    【解决方案1】:

    SQL2008+

    尝试以下解决方案:

    DECLARE @TableA TABLE (
        Id          INT NOT NULL PRIMARY KEY,
        __ParentId  INT NULL,
        [Priority]  INT NULL
    );
    
    INSERT @TableA (Id, __ParentId, [Priority])
    VALUES 
    (1 ,NULL,NULL),   
    (2 ,1   ,100 ),   
    (3 ,1   ,300 ),   
    (4 ,1   ,200 ),   
    (5 ,4   ,100 ),   
    (6 ,4   ,200 ),   
    (7 ,6   ,100 ),   
    (8 ,5   ,100 ),   
    (9 ,5   ,200 ),   
    (10,9   ,100 ),   
    (11,5   ,50  );
    
    WITH CteRecursive
    AS (
        SELECT  a.Id, a.__ParentId, a.[Priority], CONVERT(HIERARCHYID, '/' + LTRIM(a.Id) + '/') AS HID
        FROM    @TableA a
        WHERE   a.__ParentId IS NULL
        UNION ALL 
        SELECT  cld.Id, cld.__ParentId, cld.[Priority], CONVERT(HIERARCHYID, prt.HID.ToString() + LTRIM(cld.[Priority]) + '/') AS HID
        FROM     CteRecursive prt -- Parent
        JOIN @TableA cld ON prt.Id = cld.__ParentId -- Child
        WHERE   cld.__ParentId IS NOT NULL
    )
    SELECT *, r.HID.ToString() AS HIDToString FROM CteRecursive r
    ORDER BY r.HID ASC
    

    结果:

    Demo

    注意 #1:此解决方案使用 HIERARCHYID 排序的一个属性:HID 值使用深度优先方法排序(这意味着先是父级,然后是所有子级)。

    给定两个 hierarchyid 值 a 和 b,a 小于 b 意味着 a 在 a 中位于 b 之前 树的深度优先遍历。 hierarchyid 数据类型的索引 是深度优先的顺序,并且节点彼此靠近 深度优先遍历存储在彼此附近。例如, 记录的子项存储在该记录附近。更多 信息,请参阅分层数据 (SQL Server)。

    Reference

    【讨论】:

    • 很好 - 从来没有考虑过我可以将第二个元素添加到层次结构 ID +1
    • 这个解决方案也太漂亮了!!! - 非常感谢 - 我从没想过/不知道要进行真正的 BRILLIANT 文本连接。这同样有效,我无法将您的解决方案标记为解决方案的唯一原因是因为我必须选择一个,而约翰回答得更快......不过,我真的非常感谢你!!!
    • @JohnBus​​tos 接受最适合您的答案。
    • @JohnCappelletti,这就是挑战 - 他们都工作得很好,我只能选择一个作为解决方案,但也要非常感谢 Bogdan 的解决方案和工作!
    • @Bogdan,确实,经过一段时间的战斗,HierarchyId 绝对是要走的路......一个真正出色的解决方案! - 谢谢!!
    【解决方案2】:

    这是一个标准的递归 cte,但有点扭曲。我们添加一个 SEQUENCE,它是由(在这种情况下)优先级排序的 row_numbers 复合字符串。

    示例

    Declare @YourTable Table ([Id] varchar(50),[__ParentId] varchar(50),[Priority] varchar(50))
    Insert Into @YourTable Values
     (1,NULL,NULL)
    ,(2,1,100)
    ,(3,1,300)
    ,(4,1,200)
    ,(5,4,100)
    ,(6,4,200)
    ,(7,6,100)
    ,(8,5,100)
    ,(9,5,200)
    ,(10,9,100)
    ,(11,5,50)
    
    Declare @Top    int         = null      --<<  Sets top of Hier Try 4
    Declare @Nest   varchar(25) = '|-----'  --<<  Optional: Added for readability
    
    ;with cteP as (
          Select Seq  = cast(10000+Row_Number() over (Order by [Priority]) as varchar(500))
                ,ID
                ,__ParentId 
                ,Lvl=1
                ,Priority
          From   @YourTable 
          Where  IsNull(@Top,-1) = case when @Top is null then isnull(__ParentId ,-1) else ID end
          Union  All
          Select Seq  = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.[Priority])) as varchar(500))
                ,r.ID
                ,r.__ParentId 
                ,p.Lvl+1
                ,r.Priority
          From   @YourTable r
          Join   cteP p on r.__ParentId  = p.ID)
    Select A.ID
          ,A.__ParentId 
          ,A.Lvl
          ,A.Priority
          ,Name = Replicate(@Nest,A.Lvl-1) +cast(ID as varchar(25))
     From cteP A
     Order By Seq
    

    退货

    【讨论】:

    • @JohnBus​​tos 很高兴它有帮助
    • @JohnBus​​tos 仅供参考,在 Row_Number() 中,Order By 可以是元素的任意组合,所以如果你需要一个平局,它可以是 Priority,ID
    • John,我真的很抱歉取消了解决方案点。@Bogdan 使用 HierarchyId 的解决方案最终变得更加强大 - 我喜欢你的解决方案,但他的解决方案更适用于更多种类的问题。跨度>
    • @JohnBus​​tos 不用担心。 SO 的美妙之处在于您有多种选择,您可以选择最适合您的选择。
    猜你喜欢
    • 2012-04-23
    • 2012-12-25
    • 1970-01-01
    • 2010-10-08
    • 2014-05-19
    • 2021-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多