SQL Server 2014 - группа «Сумма рабочего часа» по дням / ночам, неделям / выходным, обычным / сверхурочным - PullRequest
1 голос
/ 06 апреля 2019

У меня есть список задач,
для каждого у меня есть TaskID, startTime и StopTime в миллисекундах с 1-1-1970 и список пользователей (#Tasks).

Мне нужно рассчитать время, затраченное каждым пользователем на задачу, разделенное на дневное / ночное время, неделю или выходные, обычное / сверхурочное время с учетом ночных часов с 22:00 до 06:00.

Конечно, есть лучшее решение, но пока я получил это:

IF OBJECT_ID('tempdb..#Tasks') IS NULL
    BEGIN
        create table #Tasks
        (
            TaskID nvarchar(50), 
            DateStart bigint, 
            DateStop bigint,
            Staff nvarchar(100)
        )

        insert into #Tasks values
        ('C001',1554181200000,1554190200000,'john,jack'),
        ('C002',1554202800000,1554212700000,'tom,john'),
        ('C003',1554228000000,1554246900000,'john,franck'),
        ('C004',1554613200000,1554626700000,'john')
    END
GO

declare
  @UserName     nvarchar(50)='john',
  @DateFrom     datetime='2019-04-01', 
  @DateTo       datetime='2019-04-30',
  @nStart       time='06:00:00',
  @nStop        time='22:00:00'

  select
    startday as [Day],
    sum([WeekDay]) as [WeekDay],
    sum([WeekNight]) as [WeekNight],
    sum([WeekendDay]) as [WeekendDay],
    sum([WeekendNight]) as [WeekendNight],
    sum([TotalMinutes]) as [TotalMinutes],
    0 WeekDayOverTime, 
    0 WeekNightOvertime, 
    0 WeekendDayOvertime, 
    0 WeekendNightOvertime,
    [UserName]
    ,timeframe
  from
  (
      select
        iif(isWeekend=1,NightMinutes,0) WeekendNight,
        iif(isWeekend=0,NightMinutes,0) WeekNight,
        iif(isWeekend=1,DayMinutes,0) WeekendDay,
        iif(isWeekend=0,DayMinutes,0) [WeekDay],
        TotalMinutes,
        username,
        startday,
        timeframe
       from 
       (
          select
            iif(Before6>0,Before6,0)+ iif(After22>0,After22,0) NightMinutes,
            TotalMinutes-iif(Before6>0,Before6,0)- iif(After22>0,After22,0) DayMinutes,
            TotalMinutes,
            startday,
            isWeekend,
            UserName,
            timeframe
            from 
            (
                Select 
                    (t.datestop-t.datestart)/60000 TotalMinutes,
                    datediff(n,convert(time,DATEADD(SECOND,t.DateStart/1000,'1970-01-01')),@nStart) Before6,
                    datediff(n,@nStop,convert(time,DATEADD(SECOND,t.DateStop/1000,'1970-01-01')))   After22,
                    iif((((DATEPART(DW, convert(datetime,DATEADD(SECOND,t.DateStart/1000,'1970-01-01'))) - 1 ) + @@DATEFIRST ) % 7) IN (0,6),1,0) isWeekend,
                    convert(varchar(10),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),126) startday,
                    STUFF(( SELECT distinct ' ' + convert(varchar(5),DATEADD(SECOND,t.DateStart/1000,'1970-01-01'),108)+'-'+convert(varchar(5),DATEADD(SECOND,t.DateStop/1000,'1970-01-01'),108) AS [text()]
                        FROM #Tasks tt
                        --WHERE tt.taskID=t.TaskID               
                        FOR XML PATH('') ), 1, 1, '' ) AS [timeframe],
                    @UserName UserName
                FROM #Tasks t
                WHERE t.Staff like '%'+@UserName+'%'
                and DATEADD(SECOND,t.DateStart/1000,'1970-01-01') between @DateFrom and @DateTo
            ) z
        ) zz
    ) zzz
    group by startday,username,timeframe
    order by startday

Мне нужно сейчас:
1) группируйте результаты по дням, суммируя WeekDay, WeekNight, WeekendDay, WeekendNight и TotalMinutes, и объединяя таймфрейм, например, 2 апреля "05: 00-07: 30 | 11: 00-13: 45 | 18: 00 -23: 00"
2) Не суммируйте время между 12:00 и 12:30 (если применимо), так как это обеденное время
3) учитывая, что после 8 часов в день это должно быть рассчитано как сверхурочное время, я должен разделить общее количество минут между временем и сверхурочным временем, но в зависимости от того, сверхурочное время приходится на дневное или ночное время или на выходные
4) в конечном итоге использовать праздничный стол

другими словами, мы должны иметь это:

    Day         TotalMinutes    WeekDay WeekNight   WeekendDay  WeekendNight    WeekDayOverTime WeekNightOvertime   WeekendDayOvertime  WeekendNightOvertime    UserName    timeframe
    02/04/2019  630               420     60            0            0                 45                75                   0                    0              john      05:00-07:30|11:00-13:45|18:00-23:00
    07/04/2019  225                0       0          165           60                  0                 0                   0                    0              john      05:00-08:45

потому что (2 апреля) у нас есть:
Первое задание:
60 минут обычного ночного времени
90 минут обычного дневного времени

Второе задание:
165 минут обычного дневного времени, но из-за времени обеда приходится только 135

Третье задание:
240 DayTime
75 NightTime
но поскольку с заданиями 1 и 2 мы суммируем 285 минут, только первые 185 минут третьего задания являются обычным дневным временем: оставшиеся 45 являются сверхурочным дневным временем, а следующие 75 ночного времени на самом деле являются овертаймом ночного времени

1 Ответ

1 голос
/ 10 апреля 2019

В этом подходе первые CTE (ProperDates) получают даты начала и окончания, затем вам не нужно повторять эту формулу для запроса.

Второй CTE (splittedMinutes) должен получить те же данные, которые вы получаете в своем текущем подходе, за исключением первого CROSS APPLY, который разделяет временные рамки, пересекающиеся с обеденным временем. Второй CROSS APPLY получает количество минут и значение выходного дня.

В третьем CTE (квалифицированные минуты) я использую оконный раздел, чтобы получить накопленные минуты и генерировать дополнительное время, когда это применимо.

В конце я использовал выборочную СУММУ для разделения дней и выходных в совокупности

;with properDates AS (
    SELECT TaskID, DATEADD(SECOND,t.DateStart/1000,'1970-01-01') as DateStart,DATEADD(SECOND,t.DateStop/1000,'1970-01-01') as DateStop, Staff
    FROM #Tasks t
    WHERE Staff LIKE '%' + @UserName + '%'
), splittedMinutes AS (
select
    CAST(p.DateStart AS DATE) as [Day],
    TotalMinutes,
    SUM(TotalMinutes) OVER (PARTITION BY CAST(p.DateStart AS DATE) ORDER BY b.start) AS cumulate,
    TotalMinutes - EarlyMinutes - LateMinutes  as DayTime,
    EarlyMinutes + LateMinutes as NightTime,
    isWeekend,
    CONVERT(VARCHAR(5),b.Start,108) + '-' + CONVERT(VARCHAR(5),b.Stop,108) as [timeframe]
    from properdates p
    cross apply (
        select CAST(p.DateStart As TIME) AS Start, @bStart as Stop WHERE CAST(p.DateStart AS TIME) < @bStart and CAST(p.DateStop AS TIME) > @bStart
        union 
        select @bStop as Start, CAST(DateStop AS TIME) AS Stop WHERE CAST(p.DateStop AS TIME) > @bStop and CAST(p.DateStart AS TIME) < @bStop
        union 
        select CAST(p.DateStart AS TIME) AS Start, CAST(p.DateStop AS TIME) AS Stop WHERE NOT (CAST(p.DateStart AS TIME) < @bStart and CAST(p.DateStop AS TIME) > @bStart) AND NOT (CAST(p.DateStop AS TIME) > @bStop and CAST(p.DateStart AS TIME) < @bStop)
    ) b
    cross apply (
        select 
        DATEDIFF(Minute, b.Start, b.Stop) as TotalMinutes,
        (DATEDIFF(Minute, CAST(b.Start AS TIME), @nStart) + ABS(DATEDIFF(Minute, CAST(b.Start AS TIME), @nStart))) / 2 as EarlyMinutes,
        (DATEDIFF(Minute, @nStop, CAST(b.Stop AS TIME)) + ABS(DATEDIFF(Minute, @nStop, CAST(b.Stop AS TIME)))) / 2 as LateMinutes,
        CASE WHEN DATEPART(DW, p.DateStart) IN (1,7) THEN 1 ELSE 0 END AS isWeekend 
    ) c
), qualifiedMinutes As (
    SELECT Day, TotalMinutes, RegularDay, RegularNight, OvertimeDay, OvertimeNight, isWeekend, timeframe
    FROM splittedMinutes
    OUTER APPLY (
        SELECT RegularDay = CASE WHEN cumulate <= @maxTime THEN DayTime WHEN DayTime - (cumulate - TotalMinutes - @maxTime) > 0 THEN ABS(cumulate - TotalMinutes - @maxTime) ELSE 0 END
    ) RD
    OUTER APPLY (
        SELECT OvertimeDay = DayTime - RegularDay
    ) OWD
    OUTER APPLY (
        SELECT RegularNight = CASE WHEN cumulate <= @maxTime THEN NightTime WHEN (cumulate - TotalMinutes - @maxTime + RegularDay) < 0 THEN NightTime + (cumulate - TotalMinutes - @maxTime + RegularDay) ELSE 0 END
    ) RWN
    OUTER APPLY (
        SELECT OvertimeNight = NightTime - RegularNight
    ) OWN
)

SELECT 
    Day,
    @UserName and UserName, 
    SUM(TotalMinutes) AS TotalMinutes,
    SUM(CASE WHEN isWeekend = 0 THEN RegularDay ELSE 0 END) AS WeekDay,
    SUM(CASE WHEN isWeekend = 0 THEN RegularNight ELSE 0 END) AS WeekNight,
    SUM(CASE WHEN isWeekend = 1 THEN RegularDay ELSE 0 END) AS WeekendDay,
    SUM(CASE WHEN isWeekend = 1 THEN RegularNight ELSE 0 END) AS WeekendNight,
    SUM(CASE WHEN isWeekend = 0 THEN OvertimeDay ELSE 0 END) AS WeekDayOverTime,
    SUM(CASE WHEN isWeekend = 0 THEN OvertimeNight ELSE 0 END) AS WeekNightOvertime,
    SUM(CASE WHEN isWeekend = 1 THEN OvertimeDay ELSE 0 END) AS WeekendDayOverTime,
    SUM(CASE WHEN isWeekend = 1 THEN OvertimeNight ELSE 0 END) AS WeekendNightOvertime,
    STUFF((SELECT '|' + timeframe FROM qualifiedMinutes tt WHERE tt.Day = q.Day ORDER BY timeframe FOR XML PATH('') ), 1, 1, '' ) AS [timeframe]
FROM qualifiedMinutes q
GROUP BY Day
...