TSQL:尝试将Cursor方法转换为递归CTE,获得比expec更多的行

时间:2018-03-12 10:06:59

标签: tsql common-table-expression

我不会在SQL中编写那么多代码,并且需要做一些相当棘手的累积计算来计算疲劳分数'对于轮班工作的员工,随着时间累积得分,但也根据他们在轮班之间休息了多少小时而向下调整。

对于相关表格中的每一行,我有一个开始时间和一个结束时间,然后我计算差异,使用一个函数来计算这些时间之间每小时的疲劳分数(函数总和这些并返回一个总天数),然后计算这个疲劳分数在几天内的累积总和,当该人有一个体面的休息时,将其向下调整相当大的百分比。

我有一个可行的Cursor方法,但我很想尝试CTE方法 - 不是因为我一定认为它会更快,而是因为我之前从未做过递归CTE而我认为它这将是一个很好的学习和可能更整洁的代码。

但是我对CTE做了些蠢事......因为在结果集中,我的测试对象每天都会获得越来越多的行数。即在第1天返回1行,第2天2,第3天3,等等。

这是我的Cursor方法,它运作得很好。 (它使用CTE构造游标操作的查询。)

DECLARE @EmployeeId AS NVARCHAR(8)
DECLARE @Actual_start AS DATETIME;
DECLARE @Actual_end AS DATETIME;
DECLARE @NextShift AS DATETIME;
DECLARE @HoursRest AS INT;
DECLARE @TotalWork_hrs AS INT;
DECLARE @Cumulative AS DECIMAL(5,2);
DECLARE @Cumulative_PREV AS DECIMAL(5,2);
DECLARE @DailyFatigue AS FLOAT;
DECLARE @FatigueDecay AS Decimal(3,2);

DECLARE @MyCursor AS CURSOR;

SET @Cumulative_PREV = 0;
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results;
CREATE TABLE #Results(
    EmployeeID NVARCHAR(8)
    , Actual_start DATETIME
    , Actual_end DATETIME
    , NextShift DATETIME
    , HoursRest INT
    , TotalWork_hrs INT
    , DailyFatigue FLOAT
    , FatigueDecay DECIMAL(3,2)
    , Cumulative FLOAT
    );


SET @MyCursor  = CURSOR FOR

    WITH CTE_1 AS
    (SELECT  
            [EmployeeId]
            ,[Actual_start]
            ,[Actual_end]
            ,LEAD([Actual_start]) OVER (PARTITION BY [EmployeeId] ORDER BY [EmployeeId], [Actual_start]) AS 'NextShift'
            ,[TotalWork_hrs]
            ,[Shift_Order]

        FROM [HRBI].[dbo].[WFTM_Fatigue_Current]
        WHERE 
            [Workday] = 'WD'
            AND [Actual_start]> DATEADD(dd,-90,GETDATE())
            AND [EmployeeId] = '00110838')

, CTE_2 AS
    (SELECT     
        [EmployeeId]
        ,[Actual_start]
        ,[Actual_end]
        ,[NextShift]
        , CONVERT(INT,COALESCE([NextShift],[Actual_end])-[Actual_end])*24 AS [HoursRest]
        ,[TotalWork_hrs]
        ,[Shift_Order]
    FROM CTE_1)

, CTE_3  AS
    (SELECT
        [EmployeeId]
        ,[Actual_start]
        ,[Actual_end]
        ,[NextShift]
        ,[HoursRest]
        ,[TotalWork_hrs]
        ,[Shift_Order]
        ,dbo.ufn_GetDailyFatigue([Actual_start], [Actual_end]) AS [DailyFatigue]
        , CASE 
            WHEN [HoursRest] > 16 THEN 1-POWER(0.50, ((HoursRest+8)/24-1) )
          ELSE 0
          END  AS [FatigueDecay]
          , ROW_NUMBER() OVER (ORDER BY [EmployeeId], [Actual_start]) AS ROW#
    FROM CTE_2 )

SELECT [EmployeeId]
        ,[Actual_start]
        ,[Actual_end]
        ,[NextShift]
        ,[HoursRest]
        ,[TotalWork_hrs]
        ,[DailyFatigue]
        ,[FatigueDecay] 
FROM CTE_3


OPEN @MyCursor;
FETCH NEXT FROM @MyCursor INTO @EmployeeId, @Actual_start, @Actual_end,  @NextShift, @HoursRest,  @TotalWork_hrs, @DailyFatigue, @FatigueDecay;


WHILE @@FETCH_STATUS = 0
BEGIN
 SET @Cumulative = @TotalWork_hrs + @Cumulative_PREV * (1-@FatigueDecay);
 SET @Cumulative_PREV = @Cumulative;
 INSERT INTO #Results ([EmployeeID], [Actual_start],[Actual_end], [NextShift], [HoursRest], [TotalWork_hrs], [DailyFatigue], [FatigueDecay], [Cumulative])  
 VALUES(@EmployeeId, @Actual_start, @Actual_end,  @NextShift, @HoursRest,  @TotalWork_hrs, @DailyFatigue, @FatigueDecay, @Cumulative)
 FETCH NEXT FROM @MyCursor INTO @EmployeeId, @Actual_start, @Actual_end,  @NextShift, @HoursRest,  @TotalWork_hrs, @DailyFatigue, @FatigueDecay;
END

CLOSE @MyCursor;
DEALLOCATE @MyCursor;

SELECT * FROM #Results
ORDER BY EmployeeID, Actual_start

它完全返回我想要的东西。请注意突出显示:这是一个例子,当工作人员在前一班次与此班次之间有一个不错的休息时间,因此他们的累积分数已经调低。

enter image description here

这是我使用递归CTE的尝试,只是因为它只是在其他CTE的末尾增加了一个CTE,并且没有任何现在多余的CURSOR东西:< / p>

WITH CTE_1 AS
    (SELECT  
            [EmployeeId]
            ,[Actual_start]
            ,[Actual_end]
            ,LEAD([Actual_start]) OVER (PARTITION BY [EmployeeId] ORDER BY [EmployeeId], [Actual_start]) AS 'NextShift'
            ,[TotalWork_hrs]
            ,[Shift_Order]
            , ROW_NUMBER() OVER (PARTITION BY [EmployeeId] ORDER BY [EmployeeId], [Actual_start]) AS ROW#

        FROM [HRBI].[dbo].[WFTM_Fatigue_Current]
        WHERE 
            [Workday] = 'WD'
            AND [Actual_start]> DATEADD(dd,-3,GETDATE())
            AND [EmployeeId] = '00110838')

, CTE_2 AS
    (SELECT     
        [EmployeeId]
        ,[Actual_start]
        ,[Actual_end]
        ,[NextShift]
        ,[TotalWork_hrs]
        ,[Shift_Order]
        ,[ROW#]
        , CONVERT(INT,COALESCE([NextShift],[Actual_end])-[Actual_end])*24 AS [HoursRest]
    FROM CTE_1)

, CTE_3  AS
    (SELECT
        [EmployeeId]
        ,[Actual_start]
        ,[Actual_end]
        ,[NextShift]
        ,[TotalWork_hrs]
        ,[Shift_Order]
        ,[ROW#]
        ,[HoursRest]
        ,dbo.ufn_GetDailyFatigue([Actual_start], [Actual_end]) AS [Fatigue]
        , CASE 
            WHEN [HoursRest] > 16 THEN 1-POWER(0.50, ((HoursRest+8)/24-1) )
          ELSE 0
          END  AS [FatigueDecay]

    FROM CTE_2 ), 

 CTE_4  AS
    (SELECT [EmployeeId]
        ,[Actual_start]
        ,[Actual_end]
        ,[NextShift]
        ,[TotalWork_hrs]
        ,[Shift_Order]
        ,[ROW#]
        ,[HoursRest]
        ,[Fatigue]
        ,[FatigueDecay]
        ,[Cumulative] = [Fatigue]


    FROM CTE_3

    UNION ALL
    SELECT  
        new.[EmployeeId]
        ,new.[Actual_start]
        ,new.[Actual_end]
        ,new.[NextShift]
        ,new.[TotalWork_hrs]
        ,new.[Shift_Order]
        ,new.[ROW#]
        ,new.[HoursRest]
        ,new.[Fatigue]
        ,new.[FatigueDecay]
        ,[Cumulative] = new.[Fatigue] + base.Cumulative*new.FatigueDecay

    FROM CTE_3 new

    INNER JOIN CTE_4 base
    ON base.EmployeeId = new.EmployeeId 
    AND new.ROW# = base.ROW# + 1 
    )


SELECT  * FROM CTE_4
ORDER BY Actual_start

OPTION (MAXRECURSION 0)

...并且根据下面的屏幕截图,我在那里有一个ROW#列,显示返回的行数因某些原因而不断增加,而且我的累计计算没有工作

enter image description here

有人可以找到我在这里出错的地方吗?

1 个答案:

答案 0 :(得分:1)

啊,我终于看到了我的所作所为。忘了将初始CTE限制在一行,即从CTE_4中的初始选择中错过“WHERE ROW#= 1”