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) 但是当我想要订单时我需要它返回
所以我需要的订单
- 50
- 51
- 56
- 52
- 53
- 57
- 54
- 55
测试数据的正确顺序
这是我一直在使用的代码
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 的文本输出模式下尝试此查询。这样你就可以看出区别
我正在尝试(但失败了)正确排序我的递归 CTE。我的 table 由一个父子结构组成,其中一项任务可以在各种不同的层次上与另一项任务相关。
例如,我可以创建一个任务(这是父任务),然后从这个任务创建一个子任务,然后从那个子任务创建一个子任务,依此类推..
下面是我收录的一些测试数据。目前它按 Path
按字母顺序排序。
所以如果我要创建一个任务。它会为我提供该任务的 TaskID(比如 50)——然后我可以为该主要任务创建 5 个子任务(51、52、53、54、55)。然后我可以将子任务添加到 5 个子任务 (51->56) (53->57) 但是当我想要订单时我需要它返回
所以我需要的订单
- 50
- 51
- 56
- 52
- 53
- 57
- 54
- 55
- 51
测试数据的正确顺序
这是我一直在使用的代码
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在 SSMS 的文本输出模式下尝试此查询。这样你就可以看出区别) ,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]