想出了如何去做。这个方法有点复杂,也许其他人可以想出一个更简洁的版本。
该方法包括四个步骤:
对给定项目的所有任务运行 ROW_NUMBER 函数。按 ParentId 进行分区,以便给定父级的所有子任务编号为 1、2、3、4 等。这适用于任务层次结构的所有级别;
使用递归 CTE(公用表表达式)将任务层次结构从叶级向上遍历到顶部。这将从 TimeCode 表中的父子关系构建任务层次结构。最初我试图在此处包含 ROW_NUMBER 函数,但由于 Microsoft 实现 CTE 的方式,这不起作用;
在步骤 2 中建立的结构中添加 HIERARCHYID 列;
对记录集执行自连接以获取结构中每个节点的所有子节点。按父节点分组,并对每个子节点记录的时间求和。请注意,HIERARCHYID 方法 IsDescendantOf 不仅返回节点的子节点,还返回节点本身。因此,如果已针对父任务以及子任务记录了任何时间,则它将包含在该父节点的总时间中。
这是脚本:
-- Cannot include a ROW_NUMBER function within the recursive member of the
-- common table expression as SQL Server recurses depth first. ie SQL
-- Server recurses each row separately, completing the recursion for a
-- given row before starting the next.
-- To get around this, use ROW_NUMBER outside the common table expression.
DECLARE @tblTask TABLE (TimeCodeId INT, ParentId INT, ProjectID INT,
Level INT, TaskIndex VARCHAR(12), Duration FLOAT);
INSERT INTO @tblTask (TimeCodeId, ParentId, ProjectID,
Level, TaskIndex, Duration)
SELECT tc.TimeCodeId,
tc.ParentId,
CASE
WHEN tc.ParentId IS NULL THEN tc.ReferenceId1
ELSE tc.ReferenceId2
END AS ProjectID,
1 AS Level,
CAST(ROW_NUMBER() OVER (PARTITION BY tc.ParentId
ORDER BY tc.[Description]) AS VARCHAR(12))
AS TaskIndex,
ts.Duration
FROM Time.TimeCode tc
LEFT JOIN
( -- Get time sub-totals for each task.
SELECT TimeCodeId,
SUM(Duration) AS Duration
FROM Time.Timesheet
WHERE ReferenceId2 IN (12196, 12198)
GROUP BY TimeCodeId
) ts
ON tc.TimeCodeId = ts.TimeCodeId
WHERE ReferenceId2 IN (12196, 12198)
ORDER BY [Description];
DECLARE @tblHierarchy TABLE (HierarchyNode HIERARCHYID,
Level INT, Duration FLOAT);
-- Common table expression that builds up the task hierarchy recursively.
WITH cte_task_hierarchy AS
(
-- Anchor member.
SELECT t.TimeCodeId,
t.ParentID,
t.ProjectID,
t.Level,
CAST('/' + t.TaskIndex + '/' AS VARCHAR(200)) AS HierarchyNodeText,
t.Duration
FROM @tblTask t
UNION ALL
-- Dummy root node for HIERARCHYID.
-- (easier to add it after another query so don't have to cast the
-- NULLs to data types)
SELECT NULL AS TimeCodeId,
NULL AS ParentID,
NULL AS ProjectID,
0 AS Level,
CAST('/' AS VARCHAR(200)) AS HierarchyNodeText,
NULL AS Duration
UNION ALL
-- Recursive member that walks up the task hierarchy.
SELECT tp.TimeCodeId,
tp.ParentID,
th.ProjectID,
th.Level + 1 AS Level,
CAST('/' + tp.TaskIndex + th.HierarchyNodeText AS VARCHAR(200))
AS HierarchyNodeText,
th.Duration
FROM cte_task_hierarchy th
JOIN @tblTask tp ON th.ParentID = tp.TimeCodeId
)
INSERT INTO @tblHierarchy (HierarchyNode,
Level, Duration)
SELECT hierarchyid::Parse(cth.HierarchyNodeText),
cth.Level, cth.Duration
FROM cte_task_hierarchy cth
-- This filters recordset to exclude intermediate steps in the recursion
-- - only want the final result.
WHERE cth.ParentId IS NULL
ORDER BY cth.HierarchyNodeText;
-- Show the task hierarchy.
SELECT *, HierarchyNode.ToString() AS NodeText
FROM @tblHierarchy;
-- Calculate the sub-totals for each task in the hierarchy.
SELECT t1.HierarchyNode.ToString() AS NodeText,
COALESCE(SUM(t2.Duration), 0) AS DurationTotal
FROM @tblHierarchy t1
JOIN @tblHierarchy t2
ON t2.HierarchyNode.IsDescendantOf(t1.HierarchyNode) = 1
GROUP BY t1.HierarchyNode;
结果:
第一个记录集(具有 HIERARCHYID 列的任务结构):
HierarchyNode Level Duration NodeText
------------- ----- -------- --------
0x 0 NULL /
0x58 1 NULL /1/
0x5AC0 2 12.15 /1/1/
0x5AD6 3 8.92 /1/1/1/
0x5ADA 3 11.08 /1/1/2/
0x5ADE 3 7 /1/1/3/
0x5B40 2 182.18 /1/2/
0x5B56 3 233.71 /1/2/1/
0x5B5A 3 227.27 /1/2/2/
0x5BC0 2 45.4 /1/3/
0x68 1 NULL /2/
0x6AC0 2 8.5 /2/1/
0x6B40 2 2.17 /2/2/
0x6BC0 2 8.91 /2/3/
0x6C20 2 1.75 /2/4/
0x6C60 2 60.25 /2/5/
第二个记录集(每个任务都有小计的任务):
NodeText DurationTotal
-------- -------------
/ 809.29
/1/ 727.71
/1/1/ 39.15
/1/1/1/ 8.92
/1/1/2/ 11.08
/1/1/3/ 7
/1/2/ 643.16
/1/2/1/ 233.71
/1/2/2/ 227.27
/1/3/ 45.4
/2/ 81.58
/2/1/ 8.5
/2/2/ 2.17
/2/3/ 8.91
/2/4/ 1.75
/2/5/ 60.25