【问题标题】:recursive sql function with rollup logic?具有汇总逻辑的递归sql函数?
【发布时间】:2010-12-07 16:26:15
【问题描述】:

我有一个 SQL,它使用递归 CTE 来扩展自引用员工表,构建一个按用户和严重性级别聚合的缺陷结果集。

这是我的 CTE:

    ALTER FUNCTION [dbo].[fnGetEmployeeHierarchyByUsername] 
(    
  @NTID varchar(100) = null
)  
RETURNS TABLE  
AS  
RETURN  
(  
  WITH yourcte AS  
  (  
    SELECT EmployeeId, ManagerNTID, ManagerID, NTID, FullName--, Name  
    FROM Employees  
    WHERE NTID = @NTID
    UNION ALL  
    SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID, e.FullName--, e.Name  
    FROM Employees e  
    JOIN yourcte y ON e.ManagerNTID = y.NTID
  )  
SELECT EmployeeId, ManagerID, NTID, FullName--, Name  
FROM yourcte  
)

这是我用于汇总用户缺陷的 SQL:

SELECT e.FullName, Urgent, High, Medium, Low
FROM fnGetEmployeeHierarchyByUsername ('ssalvati') e
LEFT OUTER JOIN(
    SELECT [AssignedTo],
           SUM([1-Urgent]) AS Urgent,
           SUM([2-High]) AS High,
           SUM([3-Medium]) AS Medium,
           SUM([4-Low]) AS Low
      FROM (SELECT [AssignedTo],[BusinessSeverity] FROM Defects WHERE Status <> 'Closed') D
     PIVOT (COUNT([BusinessSeverity]) FOR [BusinessSeverity] IN ([1-Urgent],[2-High],[3-Medium],[4-Low])) V
    GROUP BY [AssignedTo]) AS def
ON e.ntid = def.[AssignedTo]

我想要一个将用户名作为参数的 porc,并生成类似于上述 SQL 的结果,但具有 2 个增强功能:

  1. 我需要它来列出作为参数传入的用户,以列为结果集的第一条记录。

  2. 我需要向经理报告的员工只显示一层深度,而不是显示完整的树。第一级应该是所有底层缺陷的汇总,这些缺陷分配给汇总到所有第一级用户的人员。换句话说,我不想像现在这样在管理器下显示一整棵树,我需要它只显示一个深度但所有级别的缺陷总和。

想法?

【问题讨论】:

    标签: sql sql-server-2005


    【解决方案1】:

    这没有经过测试,因为我没有在这里安装 mssql,也没有你的数据,但是,我认为它应该是正确的,至少可以将你推向一个有用的方向。

    首先,您需要更改 UDF 中的查询以提供两条额外的信息。您的聚合崩溃的“最高”员工(我认为您说的是第一个直接报告,而不是最高员工),以及整体深度。因此:

    WITH yourcte AS  
      (  
        SELECT EmployeeId, ManagerNTID, ManagerID, NTID, FullName, 0 as Depth, ntid as Topmost  
        FROM Employees  
        WHERE NTID = @NTID
        UNION ALL  
        SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID, e.FullName, y.Depth+1, case when y.depth = 0 then e.ntid else y.Topmost end
        FROM Employees e  
        JOIN yourcte y ON e.ManagerNTID = y.NTID
      )  
    SELECT EmployeeId, ManagerID, NTID, FullName, Depth, Topmost  
    FROM yourcte
    

    然后,您的实际查询需要一些额外的细节来提取该信息并使用它

    SELECT 
      e.FullName, 
      Urgent, 
      High, 
      Medium, 
      Low
    FROM fnGetEmployeeHierarchyByUsername ('ssalvati') e
    LEFT OUTER JOIN(
        SELECT [AssignedTo],
               SUM([1-Urgent]) AS Urgent,
               SUM([2-High]) AS High,
               SUM([3-Medium]) AS Medium,
               SUM([4-Low]) AS Low
          FROM (SELECT [AssignedTo],[BusinessSeverity] FROM Defects WHERE Status <> 'Closed') D
          join fnGetEmployeeHierarchyByUsername ('ssalvati') e2 on d.AssignedTo = e2.ntid
         PIVOT (COUNT([BusinessSeverity]) FOR [BusinessSeverity] IN ([1-Urgent],[2-High],[3-Medium],[4-Low])) V
         where e2.TopMost = e.ntid
        GROUP BY [AssignedTo]) AS def
    ON e.ntid = def.[AssignedTo]
    where e.Depth <= 1
    

    对 UDF 的双重调用可能有点昂贵,因此您可能需要考虑将其放入存储过程中并使用临时表来捕获要加入的 UDF 的结果。

    另请注意,UDF 可以采用额外参数来确定“最顶层”的深度,使其更普遍,因为它目前是硬编码形式。

    【讨论】:

    • 我正在测试这个解决方案,似乎已经发现了这种情况……我把它改成这样:“当 y.depth = 0 then y.Topmost else e.ntid end 时的情况”跨度>
    • 还有 1 个错误...消息 4104,级别 16,状态 1,第 1 行 无法绑定多部分标识符“e2.TopMost”。 Msg 4104, Level 16, State 1, Line 1 无法绑定多部分标识符“e.ntid”。
    【解决方案2】:

    如果您修改了 cte 以包含深度,即

    WITH yourcte AS  
      (  
        SELECT EmployeeId, ManagerNTID, ManagerID, NTID, FullName, 0 AS Depth
        FROM Employees  
        WHERE NTID = @NTID
        UNION ALL  
        SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID, e.FullName, y.Depth + 1
        FROM Employees e  
        JOIN yourcte y ON e.ManagerNTID = y.NTID
      )
    

    然后您可以按深度对输出进行排序(因为输入参数中的用户应处于深度为零)。使用它,您还应该能够限制返回的深度并聚合深度 >= 1

    的缺陷

    编辑

    使用我在上面添加的 SQL,您基本上希望将所有缺陷汇总到级别 1 的用户?因此,此级别用户的 NTID 成为深度 >= 1 的所有记录的分组。下面对 cte 的另一个编辑将 NTID 添加为 GroupingID,您可以使用它来分组/rollup

    WITH yourcte AS  
      (  
        SELECT EmployeeId, ManagerNTID, ManagerID, NTID
              ,FullName, 0 AS Depth, NTID as GroupingID
        FROM Employees  
        WHERE NTID = @NTID
        UNION ALL  
        SELECT e.EmployeeId, e.ManagerNTID, e.ManagerID, e.NTID
              ,e.FullName, y.Depth + 1, CASE
                                           WHEN y.Depth + 1 = 1 THEN e.NTID
                                           ELSE y.GroupingId
                                        END
        FROM Employees e  
        JOIN yourcte y ON e.ManagerNTID = y.NTID
      )
    

    【讨论】:

    • 现在如何汇总 1 级数据中的其余缺陷?
    • 我已经添加了深度,并且效果很好 - 谢谢。但我仍然对如何让汇总工作感到困惑。那是第二个 CTE 吗?我希望所有 1 级记录都是它们下所有子级别的汇总(总和)。因此,如果一条记录是 1 级,则它可能有 3 个级别,应该对其值进行总结和呈现
    【解决方案3】:

    这是一个很长的虚拟方法。我让它工作,但解决方案可能会好得多。我希望有人会发布一个 SQL2005 方法来完成这项工作......

        alter PROC sel_DefectReportByManagerNTID_rollup
    (@ManagerNTID NVARCHAR(100))    
    AS
    
    CREATE TABLE #DefectCounts
    (
    id INT IDENTITY(1, 1) ,
    MgrRolledInto NVARCHAR(100) NULL,
    AltBusinessSeverity NVARCHAR(100) NULL,
    DefectCount INT NULL
    );
    
    
    CREATE TABLE #directReports
    (
    pk INT IDENTITY(1, 1) ,
    directReportNTID NVARCHAR(100) NULL
    );
    
    INSERT INTO #directReports
    SELECT NTID FROM Employees WHERE ManagerNTID = @ManagerNTID
    --select * from #directReports
    
    DECLARE @maxPK INT;
    SELECT @maxPK = MAX(PK) FROM #directReports
    
    DECLARE @pk INT;
    SET @pk = 1
    
    
    INSERT INTO #DefectCounts (MgrRolledInto,AltBusinessSeverity,DefectCount)
    SELECT @ManagerNTID, d.AltBusinessSeverity, COUNT(*)
            FROM Defects d
                JOIN StatusCode C ON C.CodeName = d.Status AND c.scid = 10
            WHERE d.AssignedTo = @ManagerNTID
            GROUP BY d.AltBusinessSeverity
    
    
    WHILE @pk <= @maxPK
    BEGIN
        /* Get one direct report at a time to aggregate their defects under them... */
        DECLARE @dirRptNTID NVARCHAR(100);
        SET @dirRptNTID = (SELECT directReportNTID
                            FROM #directReports
                            WHERE PK = @pk)
    
    
        INSERT INTO #DefectCounts (MgrRolledInto,AltBusinessSeverity,DefectCount)
            SELECT @dirRptNTID, d.AltBusinessSeverity, COUNT(*)
            FROM Defects d
                JOIN StatusCode C ON C.CodeName = d.Status AND c.scid = 10
                JOIN (SELECT * FROM fnGetEmployeeHierarchyByUsername(@dirRptNTID) ) emp ON emp.NTID = d.AssignedTo
            WHERE d.AssignedTo IS NOT NULL
            GROUP BY d.AltBusinessSeverity
    
        SELECT @pk = @pk + 1
    END
    
    
    
    SELECT  e.FullName,     
      isnull(Urgent,0) as Urgent,     
      isnull(High,0) as High,     
      isnull(Medium,0) as Medium,    
      isnull(Medium3000,0) as Medium3000,    
      isnull(Low,0) as Low    
    FROM (  select * from fnGetEmployeeHierarchyByUsername (@ManagerNTID) where depth <= 1) e    
    left outer join (
                        SELECT  MgrRolledInto,    
                                SUM([1-Urgent]) AS Urgent,    
                                SUM([2-High]) AS High,    
                                SUM([3-Medium]) AS Medium,    
                                SUM([3-Medium (3000)]) AS Medium3000,  
                                SUM([4-Low]) AS Low    
                        FROM #DefectCounts dfs
                        PIVOT 
                        (sum(DefectCount) FOR AltBusinessSeverity IN ([1-Urgent],[2-High],[3-Medium],[3-Medium (3000)],[4-Low])) V    
                        GROUP BY MgrRolledInto
                    ) def_data on def_data.MgrRolledInto = e.NTID
    order by e.depth
    

    【讨论】:

      猜你喜欢
      • 2012-12-12
      • 2021-11-18
      • 2021-07-20
      • 2013-01-10
      • 2019-07-15
      • 2015-06-01
      • 1970-01-01
      • 2020-07-23
      • 1970-01-01
      相关资源
      最近更新 更多