SQL:如何在没有游标的情况下跨列传播值,运行总计?

时间:2017-08-04 13:25:43

标签: sql sql-server tsql running-total

我有3个注册:

ID      RegisteredHours
1       7                   
2       11                  
3       6                   

小时7,11和6应根据规则在假期,工作​​时间和夜晚之间分开或分开:

  1. 假期< = 6小时
  2. WorkHours< = 11小时
  3. 夜晚<= 7小时
  4. 首先应该填写假期,然后是WorkHours,然后是Nights。 预期的结果是:

    ID  RegisteredHours     Holidays    WorkHours       Nights      [Comment]
    1   7                   6           1               0           6 hours of 7 can go to Holidays so the remaining 1 hour goes to WorkHours. Holidays are now filled (Sum=6).
    2   11                  0           10              1           10 hours of 11 can go to WorkHours so the remaining 1 hour goes to Nights. WorkHours are now filled (Sum=11).
    3   6                   0           0               6           All 6 hours can go to Nights. Nights are now filled (Sum=7)
    

    如何在不使用游标和使用运行总计的情况下计算?以下是要继续的代码:

    if object_id('Regs') is not null drop table Regs
    go
    
    create table Regs
    (
        ID int,
        RegisteredHours int
    )
    insert into Regs values (1, 7)
    insert into Regs values (2, 1)
    insert into Regs values (3, 6)
    
    select  *,
            6 HolidayHoursMax,
            11 WorkHoursMax,
            7 NightsHoursMax
    from Regs r
    

2 个答案:

答案 0 :(得分:0)

这会产生预期的效果。您需要针对更大的数据集进行测试,并根据结果进行调整...

IF OBJECT_ID('tempdb..#Regs', 'U') IS NOT NULL 
DROP TABLE #Regs;

CREATE TABLE #Regs (
    ID INT NOT NULL PRIMARY KEY CLUSTERED,
    RegisteredHours INT NOT NULL 
    );

INSERT #Regs (ID, RegisteredHours) VALUES
    (1, 7), (2, 11), (3, 6);

--  SELECT * FROM #Regs r

--========================================

SELECT 
    r.ID,
    r.RegisteredHours,
    Holidays = CASE 
                    WHEN r.ID = 1 AND r.RegisteredHours <= 6 THEN r.RegisteredHours 
                    WHEN r.ID = 1 AND r.RegisteredHours > 6 THEN 6
                    ELSE 0
                END,
    WorkHours = CASE
                    WHEN r.ID = 1 AND r.RegisteredHours BETWEEN 7 AND 16 THEN r.RegisteredHours - 6
                    WHEN r.ID = 1 AND r.RegisteredHours > 16 THEN 10
                    WHEN r.ID = 2 AND r.RegisteredHours <= 10 THEN r.RegisteredHours
                    WHEN r.ID = 2 AND r.RegisteredHours > 10 THEN 10
                    ELSE 0
                END,
    Nights =    CASE 
                    WHEN r.ID = 1 AND r.RegisteredHours > 16 THEN r.RegisteredHours - 16
                    WHEN r.ID = 2 AND r.RegisteredHours > 10 THEN r.RegisteredHours - 10
                    WHEN r.ID = 3 THEN r.RegisteredHours
                    ELSE 0
                END 
FROM 
    #Regs r;

答案 1 :(得分:0)

请检查以下解决方案。也许它可以用较少的CTE来实现,但至少乍一看它产生的结果和你假设的结果相同,而且它对于ID的数量等是灵活的:

DECLARE @Regs TABLE
(
    ID INT IDENTITY(1,1),
    RegisteredHours int
)

insert into @Regs values (7);
insert into @Regs values (11);
insert into @Regs values (6);

insert into @Regs values (2);
insert into @Regs values (1);
insert into @Regs values (3);
insert into @Regs values (1);
insert into @Regs values (1);
insert into @Regs values (7);
insert into @Regs values (5);
insert into @Regs values (1);
insert into @Regs values (4);
insert into @Regs values (3);


DECLARE @HolidayHoursMax int = 6;
DECLARE @WorkHoursMax int = 11;
DECLARE @NightHoursMax int = 7;

WITH cteHolidayBasis AS(
  SELECT r.ID
        ,r.RegisteredHours
        ,SUM(CASE
               WHEN r.RegisteredHours <= @HolidayHoursMax THEN r.RegisteredHours
               ELSE @HolidayHoursMax
             END) OVER (ORDER BY ID) AS HolidayHours
        ,@HolidayHoursMax - SUM(CASE
                                  WHEN r.RegisteredHours <= @HolidayHoursMax THEN r.RegisteredHours
                                  ELSE @HolidayHoursMax
                                END) OVER (ORDER BY ID) AS HolidayHoursRem
    FROM @Regs r
),
cteHoliday AS(
  SELECT  ID
         ,CASE
            WHEN (HolidayHoursRem = 0) AND (RegisteredHours > @HolidayHoursMax) THEN @HolidayHoursMax
            WHEN (HolidayHoursRem = 0) AND (RegisteredHours <= @HolidayHoursMax) THEN RegisteredHours
            WHEN (HolidayHoursRem > 0) THEN RegisteredHours
            WHEN (HolidayHoursRem < 0 AND LAG(HolidayHoursRem) OVER (ORDER BY ID) > 0) THEN LAG(HolidayHoursRem) OVER (ORDER BY ID)
            ELSE 0
          END AS HolidayHours
         ,RegisteredHours - CASE
                               WHEN (HolidayHoursRem = 0) AND (RegisteredHours > @HolidayHoursMax) THEN @HolidayHoursMax
                               WHEN (HolidayHoursRem = 0) AND (RegisteredHours <= @HolidayHoursMax) THEN RegisteredHours
                               WHEN (HolidayHoursRem > 0) THEN RegisteredHours
                               WHEN (HolidayHoursRem < 0 AND LAG(HolidayHoursRem) OVER (ORDER BY ID) > 0) THEN LAG(HolidayHoursRem) OVER (ORDER BY ID)
                               ELSE 0
                            END AS RemHours
    FROM cteHolidayBasis
),
cteWorkBasis AS(
  SELECT ID
        ,RemHours
        ,SUM(CASE
               WHEN RemHours <= @WorkHoursMax THEN RemHours
               ELSE @WorkHoursMax
             END) OVER (ORDER BY ID) AS WorkHours
        ,@WorkHoursMax - SUM(CASE
                               WHEN RemHours <= @WorkHoursMax THEN RemHours
                               ELSE @WorkHoursMax
                             END) OVER (ORDER BY ID) AS WorkHoursRem
    FROM cteHoliday
),
cteWork AS(
  SELECT  ID
         ,CASE
            WHEN (WorkHoursRem = 0) AND (RemHours > @WorkHoursMax) THEN @WorkHoursMax
            WHEN (WorkHoursRem = 0) AND (RemHours <= @WorkHoursMax) THEN RemHours
            WHEN (WorkHoursRem > 0) THEN RemHours
            WHEN (WorkHoursRem < 0 AND LAG(WorkHoursRem) OVER (ORDER BY ID) > 0) THEN LAG(WorkHoursRem) OVER (ORDER BY ID)
            ELSE 0
          END AS WorkHours
         ,RemHours - CASE
                       WHEN (WorkHoursRem = 0) AND (RemHours > @WorkHoursMax) THEN @WorkHoursMax
                       WHEN (WorkHoursRem = 0) AND (RemHours <= @WorkHoursMax) THEN RemHours
                       WHEN (WorkHoursRem > 0) THEN RemHours
                       WHEN (WorkHoursRem < 0 AND LAG(WorkHoursRem) OVER (ORDER BY ID) > 0) THEN LAG(WorkHoursRem) OVER (ORDER BY ID)
                       ELSE 0
                     END AS RemHours
    FROM cteWorkBasis
),
cteNightBasis AS(
  SELECT ID
        ,RemHours
        ,SUM(CASE
               WHEN RemHours <= @NightHoursMax THEN RemHours
               ELSE @NightHoursMax
             END) OVER (ORDER BY ID) AS NightHours
        ,@NightHoursMax - SUM(CASE
                                 WHEN RemHours <= @NightHoursMax THEN RemHours
                                 ELSE @NightHoursMax
                               END) OVER (ORDER BY ID) AS NightHoursRem
    FROM cteWork
),
cteNight AS(
  SELECT  ID
         ,CASE
            WHEN (NightHoursRem = 0) AND (RemHours > @NightHoursMax) THEN @NightHoursMax
            WHEN (NightHoursRem = 0) AND (RemHours <= @NightHoursMax) THEN RemHours
            WHEN (NightHoursRem > 0) THEN RemHours
            WHEN (NightHoursRem < 0 AND LAG(NightHoursRem) OVER (ORDER BY ID) > 0) THEN LAG(NightHoursRem) OVER (ORDER BY ID)
            ELSE 0
          END AS NightHours
         ,RemHours - CASE
                       WHEN (NightHoursRem = 0) AND (RemHours > @NightHoursMax) THEN @NightHoursMax
                       WHEN (NightHoursRem = 0) AND (RemHours <= @NightHoursMax) THEN RemHours
                       WHEN (NightHoursRem > 0) THEN RemHours
                       WHEN (NightHoursRem < 0 AND LAG(NightHoursRem) OVER (ORDER BY ID) > 0) THEN LAG(NightHoursRem) OVER (ORDER BY ID)
                       ELSE 0
                     END AS RemHours
    FROM cteNightBasis
)
SELECT R.ID
      ,R.RegisteredHours
      ,cH.HolidayHours
      ,cW.WorkHours
      ,cN.NightHours
      ,cN.RemHours
  FROM @Regs AS R
  JOIN cteHoliday AS cH ON R.ID = cH.ID
  JOIN cteWork AS cW ON R.ID = cW.ID
  JOIN cteNight AS cN ON R.ID = cN.ID
ORDER BY R.ID

我添加了另一个专栏&#34; RemHours&#34;在输出中 - 如果你超过MaxWork + MaxHoliday + MaxNight的总小时数。此外,我添加了几个测试用例 - 只需注释前三个插入,以便用较小的值进行测试。