Разобрался, как это сделать.Метод немного запутанный, возможно, кто-то другой может придумать более аккуратную версию.
Метод состоит из четырех шагов:
Запустите функцию ROW_NUMBER для всех задач.для данных проектов.Разделение по ParentId, чтобы все дочерние задачи данного родителя были пронумерованы 1, 2, 3, 4 и т. Д. Это работает на всех уровнях иерархии задач;
Использование рекурсивногоCTE (общее табличное выражение), чтобы пройти вверх по иерархии задач от конечного уровня до вершины.Это создаст структуру иерархии задач из отношения родитель-потомок в таблице TimeCode.Первоначально я пытался включить здесь функцию ROW_NUMBER, но это не сработало из-за способа, которым Microsoft внедрила CTE;
Добавить столбец HIERARCHYID в структуру, созданную на шаге 2;
Выполните самостоятельное объединение набора записей, чтобы получить все дочерние элементы каждого узла в структуре.Группируйте по родительскому узлу и суммируйте время, записанное для каждого дочернего узла.Обратите внимание, что метод 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