T-SQL 排序递归查询 - Parent/Child 结构

T-SQL Ordering a Recursive Query - Parent/Child Structure

我正在尝试(但失败了)正确排序我的递归 CTE。我的 table 由一个父子结构组成,其中一项任务可以在各种不同的层次上与另一项任务相关。

例如,我可以创建一个任务(这是父任务),然后从这个任务创建一个子任务,然后从那个子任务创建一个子任务,依此类推..

下面是我收录的一些测试数据。目前它按 Path 按字母顺序排序。

所以如果我要创建一个任务。它会为我提供该任务的 TaskID(比如 50)——然后我可以为该主要任务创建 5 个子任务(51、52、53、54、55)。然后我可以将子任务添加到 5 个子任务 (51->56) (53->57) 但是当我想要订单时我需要它返回

所以我需要的订单

测试数据的正确顺序

这是我一直在使用的代码

DECLARE @TaskID NUMERIC(10,0)

SET @TaskID = 38

;WITH cte AS 
(
SELECT 
    t.TaskID
    ,t.ParentID
    ,t.Title
    ,CONVERT(VARCHAR(MAX),'') AS [Nest]
    ,CONVERT(VARCHAR(MAX),'') AS [Path]
    ,t.CreatedDate
FROM 
    tasks.Tasks t
WHERE 
    t.ParentID IS NULL
    AND t.TaskID = @TaskID

UNION ALL

SELECT 
    sub.TaskID
    ,sub.ParentID
    ,sub.Title
    ,cte.[Nest] + CONVERT(VARCHAR(MAX),sub.TaskID) AS [Nest]
    ,cte.[Path] + ',' + CONVERT(VARCHAR(MAX),sub.TaskID) AS [Path]
    ,sub.CreatedDate
FROM 
    tasks.Tasks sub
    INNER JOIN cte ON cte.TaskID = sub.ParentID
)

SELECT 
    TaskID
    ,ParentID
    ,Title
    ,Nest
    ,[Path]
    ,CreatedDate
FROM (
SELECT 
    cte.TaskID
    ,cte.ParentID
    ,cte.Title
    ,NULLIF(LEN(cte.[Path]) - LEN(REPLACE(cte.[Path], ',', '')),0) Nest
    ,CONVERT(VARCHAR(25),@TaskID) + cte.[Path] AS [Path]
    ,cte.CreatedDate
FROM 
    cte
)a
ORDER BY
    a.[Path]

我觉得它会非常明显,但我真的不确定如何进行。我考虑了更多的递归、函数、拆分字符串,但没有成功。

抱歉,如果我不清楚

很简单。您不需要使用任何循环或函数。我假设您已经导出 PATH 值。基于此,我得出了解决方案。

SELECT C.TASKID,  REPLICATE(' ', (LEN([PATH]) - LEN(REPLACE([PATH],',','')) + 2) ) + CONVERT(NVARCHAR(20),C.TASKID), [PATH]
FROM CTE C
ORDER BY [PATH]

最简单的方法是将键填充到固定长度。例如038,007 将在 038,012 之前排序,但是填充长度对于最大的 taskid 必须是安全的。尽管您可以保持 path 修剪以提高可读性并创建一个额外的填充字段用于排序。

一个更安全的版本是做同样的事情,但从 row_numbers 创建一个填充路径。填充大小必须足够大以支持最大数量的子项。

DECLARE @TaskID NUMERIC(10,0)

SET @TaskID = 38

declare @maxsubchars int = 3 --not more than 999 sub items

;with cte as
(
SELECT 
    t.TaskID
    ,t.ParentID
    ,t.Title
    ,0 AS [Nest]
    ,CONVERT(VARCHAR(MAX),t.taskid) AS [Path]
    ,CONVERT(VARCHAR(MAX),'') OrderPath 
    ,t.CreatedDate  
FROM 
    tasks.Tasks t
WHERE 
    t.ParentID IS NULL
    AND t.TaskID = @TaskID

union all

SELECT 
    sub.TaskID
    ,sub.ParentID
    ,sub.Title
    ,cte.Nest + 1
    ,cte.[Path] + ',' + CONVERT(VARCHAR(MAX),sub.TaskID) 
    ,cte.OrderPath + ',' + right(REPLICATE('0', @maxsubchars) + CONVERT(VARCHAR,ROW_NUMBER() over (order by  sub.TaskID)), @maxsubchars) 
    ,sub.CreatedDate
FROM 
    tasks.Tasks sub
    INNER JOIN cte ON cte.TaskID = sub.ParentID
)
select taskid, parentid, title,nullif(nest,0) Nest,Path, createddate from cte order by  OrderPath

您可能比固定子项长度更花哨,确定子项的数量并根据所述长度进行填充。或者使用基于兄弟姐妹数量的编号行并反向遍历,也许(只是吐出一些未经测试的想法),但使用简单的有序路径可能就足够了。

如果最顶层的 CTE(如以下查询中所示)是您的 table 结构,则以下代码可能是解决方案。

WITH CTE AS
(
    SELECT 7112 TASKID ,NULL PARENTID UNION ALL
    SELECT 7120 TASKID ,7112 ParanetID UNION ALL
    SELECT 7139 TASKID ,7112 ParanetID UNION ALL
    SELECT 7150 TASKID ,7112 ParanetID UNION ALL
    SELECT 23682 TASKID ,7112 ParanetID UNION ALL
    SELECT 7100 TASKID ,7112 ParanetID UNION ALL
    SELECT 23691 TASKID ,7112 ParanetID UNION ALL
    SELECT 23696 TASKID ,7112 ParanetID UNION ALL
    SELECT 23700 TASKID ,23696 ParanetID UNION ALL
    SELECT 23694 TASKID ,23691 ParanetID UNION ALL
    SELECT 23689 TASKID ,7120 ParanetID UNION ALL
    SELECT 7148 TASKID ,23696 ParanetID UNION ALL
    SELECT 7126 TASKID ,7120 ParanetID UNION ALL
    SELECT 7094 TASKID ,7120 ParanetID UNION ALL
    SELECT 7098 TASKID ,7094 ParanetID UNION ALL
    SELECT 23687 TASKID ,7094 ParanetID 

) ,RECURSIVECTE AS ( SELECT TASKID, CONVERT(NVARCHAR(MAX),convert(nvarchar(20),TASKID)) [PATH] FROM CTE WHERE PARENTID IS NULL

UNION ALL

SELECT C.TASKID, CONVERT(NVARCHAR(MAX),convert(nvarchar(20),R.[PATH]) + ',' + convert(nvarchar(20),C.TASKID)) FROM RECURSIVECTE R INNER JOIN CTE C ON R.TASKID = C.PARENTID )

SELECT C.TASKID, REPLICATE(' ', (LEN([PATH]) - LEN(REPLACE([PATH],',','')) + 2) ) + '.' + CONVERT(NVARCHAR(20),C.TASKID) FROM RECURSIVECTE C ORDER BY [PATH]

在 SSMS 的文本输出模式下尝试此查询。这样你就可以看出区别