2012-01-17 40 views
1

我被要求查詢時間記錄數據庫,以顯示爲給定項目完成的所有工作。每個項目都分解成任務,每個任務本身都可以分解爲任務。任務層次可以是任意數量的深層次。部分要求是爲層次結構中的每個任務或節點(不僅僅是葉級節點,而是所有節點,包括頂級項目節點,葉級節點和之間的所有節點)提供總時間。T-SQL中的HierarchyID集合函數

使用這樣的層次結構我認爲使用HIERARCHYID數據類型可能很有用。有沒有什麼辦法像層次結構上的ROLLUP這樣做SUM來給出層次結構中每個節點的小計?

我認爲這種層次結構上的彙總彙總是一個常見的要求,但我一直沒有找到如何去做的運氣,或者即使它是可能的。

+0

如果您可以向我們展示您正在使用的模式可能會幫助我們想出一種適合您的方法。 – mwigdahl 2012-01-17 21:56:52

+0

兩個表格,Time.TimeCode存儲項目任務的結構,Time.TimeSheet存儲每個任務記錄的時間。 – 2012-01-17 21:59:08

+0

這兩個表的更多細節:(1)Time.TimeCode包含列TimeCodeId,任務的ID; ParentId,任務父項的ID; Description和ReferenceId2,項目ID。給定項目的所有任務都具有相同的ReferenceId2。 (2)Time.Timesheet包含TimesheetId,時間條目的ID; TimeCodeId,TimeCode表的外鍵;日期,工作完成日期(日期數據類型);和Duration(持續時間),這是在任務上花費的時間(float)。 「... Id」列都是整數。 – 2012-01-17 22:16:11

回答

2

想通如何做到這一點。該方法有點複雜,也許別人可以想出一個整潔的版本。

該方法包括四個步驟:

  1. 在對給定項目的所有任務運行ROW_NUMBER功能。通過ParentId分區,以便給定父項的所有子任務都編號爲1,2,3,4等。這適用於任務層次結構的所有級別;

  2. 使用遞歸CTE(公用表表達式)從葉級到頂級遍歷任務層次結構。這將根據TimeCode表中的父子關係構建任務層次結構。最初我嘗試在這裏包含ROW_NUMBER函數,但由於微軟實施CTE的方式,這不起作用;

  3. 將HIERARCHYID列添加到在步驟2中構建的結構中;

  4. 對記錄集執行自連接以獲取結構中每個節點的所有子節點。按父節點分組並計算每個子節點記錄的時間。請注意,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 
+0

不錯的工作。將其標記爲答案;) – 2012-01-18 05:18:16

2

這是我試過的東西,它的工作正常。在這種情況下,我有表分類法,其中有ID和ParentTaxonomyID指向ID。在這個存儲過程中,我想計算與分類相關的相關問題的數量 - 但我想通過層次結構對它們進行總結。這裏是存儲過程我用

ALTER FUNCTION [dbo].[func_NumberOfQuestions]( 
@TaxonomyID INT) 
RETURNS INT 
AS 
BEGIN 

DECLARE @NChildren INT 
SELECT @NChildren = dbo.func_NumberOfTaxonomyChildren(@TaxonomyID) 

DECLARE @NumberOfQuestions INT, @NumberOfDirectQuestions INT, 
    @NumberOfChildQuestions INT 

SELECT @NumberOfDirectQuestions = COUNT(*) 
FROM ProblemTaxonomies 
WHERE TaxonomyID = @TaxonomyID 

SELECT @NumberOfChildQuestions = 0 
IF @NChildren > 0 
BEGIN 
SELECT @NumberOfChildQuestions = 
     ISNULL(SUM(dbo.func_NumberOfQuestions(id)), 0) 
FROM Taxonomies 
WHERE ParentTaxonomyID = @TaxonomyID 
END 

RETURN @NumberOfDirectQuestions + @NumberOfChildQuestions 
END 

我用T-SQL的功能,這應該是很明顯的遞歸調用 - 但使用SQL我能夠使用SUM函數爲孩子

+0

很好,很直接,謝謝。對我來說不夠遠,因爲我在層次結構中的每個節點上都有一行記錄集。爲了從你的代碼中得到結果,我想我需要傳遞一個表變量或表值參數到存儲過程或函數中,並且再次返回一個爲加總的節點添加的行。儘管如此,我懷疑這可能仍然比我做的更乾淨簡單。 – 2012-11-01 03:23:47