Расчет рабочего времени между двумя датами - включая выходные - PullRequest
0 голосов
/ 05 марта 2020

Мне было дано задание рассчитать количество рабочих часов, затраченных на доставку заказов, с момента, когда заказ был введен впервые, до момента его отправки. Для этого я не могу создать какую-либо функцию из-за проблем с разрешениями.

Рабочие часы: понедельник - пятница, 08:00 - 17:30. Суббота и воскресенье - с 08:00 до 16:00. Необходимо подвести итоги времени, затраченного на эти основные часы.

Если заказ был размещен до начала дня (8 утра), то он должен обрабатывать заказ как ввод в 8 утра. Если заказ был после конца дня (5:30 вечера MF, 4:00 S / SS), тогда обработайте, как если бы заказ был введен в конце дня. То же самое верно и для доставок.

Я искал это и нашел предыдущие ответы на SO, но обнаружил, что все ранее принятые ответы используют функции для этого.

В моем развитии этого, Я достиг этого без использования функций. Я поделюсь своим ответом ниже для будущих поисков / ссылок.

Приветствую любые отзывы о том, как это можно улучшить.

Ниже приведен пример заказа и даты доставки для работы.

IF OBJECT_ID('tempdb..#Orders') IS NOT NULL DROP TABLE #Orders;  -- Example data to prove the theory.
    CREATE TABLE #Orders (OrderDate DateTime, DeliveryDate DATETIME)
    INSERT INTO #Orders  (#Orders.OrderDate, #Orders.DeliveryDate) 
    VALUES
       (cast('2020-01-18 13:55:15.000' as datetime), cast('2020-01-19 13:35:56.110' as datetime)), 
       (cast('2020-01-18 23:47:57.000' as datetime), cast('2020-01-19 13:36:40.537' as datetime)), 
       (cast('2020-01-18 07:20:12.000' as datetime), cast('2020-01-19 13:37:41.977' as datetime)), 
       (cast('2020-01-18 08:51:46.813' as datetime), cast('2020-01-19 13:38:35.193' as datetime)), 
       (cast('2020-01-18 12:37:13.000' as datetime), cast('2020-01-19 14:24:35.927' as datetime)), 
       (cast('2020-01-18 12:59:54.000' as datetime), cast('2020-01-19 14:53:23.663' as datetime)), 
       (cast('2020-01-19 13:44:31.000' as datetime), cast('2020-01-19 14:56:47.157' as datetime)), 
       (cast('2020-01-19 13:38:19.000' as datetime), cast('2020-01-19 14:58:09.543' as datetime)), 
       (cast('2020-01-19 08:55:31.050' as datetime), cast('2020-01-20 08:17:25.073' as datetime)), 
       (cast('2020-01-18 21:16:23.000' as datetime), cast('2020-01-20 08:17:52.330' as datetime)), 
       (cast('2020-01-19 08:59:26.650' as datetime), cast('2020-01-20 08:18:05.163' as datetime)), 
       (cast('2020-01-19 08:49:24.193' as datetime), cast('2020-01-20 08:18:49.077' as datetime)), 
       (cast('2020-01-18 15:33:48.000' as datetime), cast('2020-01-20 08:26:24.387' as datetime)), 
       (cast('2020-01-18 18:45:52.000' as datetime), cast('2020-01-20 08:26:29.657' as datetime)), 
       (cast('2020-01-18 20:56:33.000' as datetime), cast('2020-01-20 08:27:25.517' as datetime)), 
       (cast('2020-01-18 08:55:53.100' as datetime), cast('2020-01-20 08:28:25.210' as datetime)), 
       (cast('2020-01-06 00:19:08.000' as datetime), cast('2020-01-20 08:28:27.197' as datetime)), 
       (cast('2020-01-18 17:38:50.000' as datetime), cast('2020-01-20 08:42:16.777' as datetime)), 
       (cast('2020-01-19 14:24:30.000' as datetime), cast('2020-01-20 08:42:37.537' as datetime)), 
       (cast('2020-01-19 12:00:01.000' as datetime), cast('2020-01-20 08:42:53.173' as datetime)), 
       (cast('2020-01-19 13:21:15.000' as datetime), cast('2020-01-20 08:43:18.517' as datetime)), 
       (cast('2020-01-19 04:11:15.000' as datetime), cast('2020-01-20 09:28:34.997' as datetime)), 
       (cast('2020-01-19 09:28:05.000' as datetime), cast('2020-01-20 09:28:51.447' as datetime)), 
       (cast('2020-01-16 22:09:49.000' as datetime), cast('2020-01-20 09:29:23.630' as datetime)), 
       (cast('2020-01-19 13:43:05.000' as datetime), cast('2020-01-20 09:29:41.997' as datetime))

1 Ответ

2 голосов
/ 05 марта 2020

Я прокомментировал SQL, как мог, чтобы объяснить свой метод на простом английском языке sh. Есть также некоторые фиктивные данные для этого.

Ниже приведено приблизительное объяснение моего метода:

Использование CTE - создавайте по одной строке данных на каждый день, когда этот запрос будет возможен когда-либо. ссылка. Это может быть легко расширено в будущем.

Затем go через каждую строку, возвращаемую из CTE, и установите количество рабочих часов для этого дня в зависимости от дня недели. Также установите дату окончания этого дня, опять же в зависимости от дня недели. Сохраните это в таблице DayRows.

Присоединитесь к таблице DayRows, где дата находится между датой заказа и датой поставки, и суммируйте общее количество рабочих минут, возможных из этих дат.

Рассчитайте Разница мин минут между началом дня и временем заказа. Рассчитайте разницу в минутах между датой доставки и концом дня.

Вычтите эти различия из суммы рабочих минут. Затем это дает общее количество рабочих минут между двумя датами. Разделитесь на 60, чтобы вернуться в часы.


---- calculates the number of working hours between order date and delivery date. Working day starts 08:00 each day. Weekdays ends at 17:30. Weekends ends at 16:00

; WITH TempDays AS 
    (SELECT CAST('2015-01-01 08:00:00' AS DATETIME) AS DateValue
    UNION ALL 
    SELECT DATEADD(DAY,1,DateValue) AS DateValue
    FROM TempDays
    WHERE 
       TempDays.DateValue <= '2035-12-31'
    ) -- Recursive CTE to give one row per day between 2015 and 2035


, DayRows as
    ( 
       SELECT
          TempDays.DateValue AS StartDay
          , CASE    
             WHEN datename(WEEKDAY,TempDays.DateValue) IN ('Saturday', 'Sunday')
             THEN DATEADD(HOUR,8,TempDays.DateValue) -- Saturday & sunday
             ELSE DATEADD(MINUTE,30,DATEADD(HOUR,9,TempDays.DateValue)) -- Weekday
             END AS EndDay
          , CASE When datename(WEEKDAY,TempDays.DateValue) IN ('Saturday', 'Sunday')
             Then 480 -- Saturday & Sunday 8 hours
             ELSE 570 -- Weekday  9.5 hours
             END AS WorkMinutes
       FROM 
          TempDays 
    ) -- This calcualtes the Start and End datetime for each day returned. If Weekend, end time is 16:00. Weekday is 17:30. All days start at 8:00



SELECT
    #Orders.Orderdate
    , #Orders.DeliveryDate
    , WorkingHours.WorkMins
    , StartHours.MinutesDayStart
    , EndHours.MinutesDayEnd
    , CAST( ((WorkingHours.WorkMins - ISNULL(StartHours.MinutesDayStart,0) - ISNULL(EndHours.MinutesDayEnd,0))  / 60.00) AS DECIMAL(32,2)) AS Working_Hours_To_Ship -- Takes total working minutes for all the days inbetween order and delivery, then removes the number of mins between start of day and order and the delivery and end of the day.

FROM 
    #Orders
    OUTER APPLY
       (
          SELECT
             SUM(DayRows.WorkMinutes)  AS WorkMins 
          FROM 
             DayRows
          WHERE 
            CAST(DayRows.StartDay AS DATE) >= CAST(#Orders.Orderdate AS DATE) AND CAST(DayRows.EndDay AS DATE) <= CAST(#Orders.DeliveryDate AS DATE) 
       ) AS WorkingHours -- Calculates the sum total of working hours for all days between the order date and delivery date, including the order and del date.


    OUTER APPLY
       (SELECT
          DATEDIFF(MINUTE,DayRows.StartDay,
                CASE 
                    WHEN CAST(#Orders.Orderdate AS TIME) < cast(DayRows.StartDay AS TIME)
                    THEN DayRows.StartDay
                    ELSE 
                       CASE 
                          WHEN CAST(#Orders.Orderdate AS TIME) > cast(DayRows.EndDay AS TIME)
                          THEN DayRows.EndDay
                          Else #Orders.Orderdate
                          End
                    End
                ) AS MinutesDayStart
       FROM 
          DayRows
       WHERE 
         CAST(DayRows.StartDay AS DATE) = CAST(#Orders.Orderdate AS DATE) 
       ) AS StartHours -- Calcualtes the number of minutes between the start of the day and the order date. This is then to be deducted off the total working hours.

    OUTER APPLY
       (SELECT
          DATEDIFF(MINUTE,
             CASE 
                WHEN CAST(#Orders.DeliveryDate AS TIME) < cast(DayRows.StartDay AS TIME) -- If the delivery was made before the start of the day, then uses start of day as delivery time.
                THEN DayRows.StartDay
                ELSE 
                    CASE 
                       WHEN CAST(#Orders.DeliveryDate AS TIME) > cast(DayRows.EndDay AS TIME)  -- If the delivery was made after the end of the day, then uses the end of the day as the delivery time
                       THEN DayRows.EndDay
                       Else #Orders.DeliveryDate
                    End
             END,      
             DayRows.EndDay
          )  AS MinutesDayEnd 
       FROM 
          DayRows
       WHERE 
         CAST(DayRows.StartDay AS DATE) = CAST(#Orders.DeliveryDate AS DATE) 
       ) AS EndHours -- Calculates the number of minutes between the delivery date and the end of the day.


WHERE 
    #Orders.Orderdate >= '2020-01-01'

option (maxrecursion 0)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...