sql-server - T-SQL 中的 HierarchyID 聚合函数

标签 sql-server sql-server-2008 t-sql hierarchyid

我被要求查询时间记录数据库,以显示给定项目完成的所有工作。每个项目都分为任务,每个任务本身又可以分为任务。任务层次结构的深度可以是任意数量。部分要求是提供层次结构中每个任务或节点的总工作时间(不仅仅是叶级节点,而是所有节点,包括顶级项目节点、叶级节点以及中间的所有节点)。

使用这样的层次结构,我认为使用 HIERARCHYID 数据类型可能会很有用。有没有什么方法可以在层次结构上执行类似于 SUM 和 ROLLUP 的操作,以给出层次结构中每个节点的小计?

我认为这种层次结构上的聚合汇总将是一个常见的要求,但我根本没有找到如何做到这一点,或者即使它是可能的。

最佳答案

知道怎么做了。方法有点复杂,也许其他人可以想出一个更简洁的版本。

该方法包括四个步骤:

  1. 对给定项目的所有任务运行 ROW_NUMBER 函数。按 ParentId 进行分区,以便给定父级的所有子任务编号为 1、2、3、4 等。这适用于任务层次结构的所有级别;

  2. 使用递归 CTE(公用表表达式)从叶级向上遍历任务层次结构到顶部。这将从时间码表中的父子关系构建任务层次结构。最初,我尝试在此处包含 ROW_NUMBER 函数,但由于 Microsoft 实现 CTE 的方式,这不起作用;

  3. 向步骤 2 中构建的结构添加 HIERARCHYID 列;

  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

关于sql-server - T-SQL 中的 HierarchyID 聚合函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8901427/

相关文章:

SQL生成表数据的XML

sql - 每当将项目添加到 Service Broker 队列时,如何执行存储过程?

sql - T-SQL问题: Query to XML

sql-server-2008 - 将 TSQL 变量分配给 SQLCMD 变量

c# - System.Data.dll 附加信息 : Invalid object name 中发生类型 'System.Data.SqlClient.SqlException' 的异常

mysql - SQL 查询不工作。 Count 也应该返回零。但即使在外部加入后也不工作

sql-server - 使用 TSQL 旋转 JPEG 图像,可能吗?

sql-server - SignalR 使用背板横向扩展

sql - 在 SQL Server 的更新语句中使用随机记录更新表?

xml - 从 Sql Server 列中的 XML 中提取数据