Я бы порекомендовал создать пользовательскую функцию, которая вычисляет разницу дат в рабочие часы согласно вашим правилам.
SELECT
Id,
MIN(Date) DateStarted,
MAX(Date) DateCompleted,
dbo.udfDateDiffBusinessHours(MIN(Date), MAX(Date)) ReactionTime
FROM
Incident
GROUP BY
Id
Я не уверен, откуда берется ваше значение Overdue
, поэтому я остановил его в своем примере.
В функции вы можете написать более выразительный SQL, чем в запросе, и вы не засоряете свой запрос бизнес-правилами, что затрудняет его поддержку.
Также функцию можно легко использовать повторно. Расширить его, чтобы включить поддержку праздников (я думаю о таблице Holidays
здесь), не будет слишком сложно. Дальнейшие уточнения возможны без необходимости изменения трудно читаемых вложенных конструкций SELECT / CASE WHEN, что будет альтернативой.
Если у меня будет время сегодня, я посмотрю, как написать пример функции.
РЕДАКТИРОВАТЬ: Вот что-то с наворотами, прозрачно рассчитывается по выходным:
ALTER FUNCTION dbo.udfDateDiffBusinessHours (
@date1 DATETIME,
@date2 DATETIME
) RETURNS DATETIME AS
BEGIN
DECLARE @sat INT
DECLARE @sun INT
DECLARE @workday_s INT
DECLARE @workday_e INT
DECLARE @basedate1 DATETIME
DECLARE @basedate2 DATETIME
DECLARE @calcdate1 DATETIME
DECLARE @calcdate2 DATETIME
DECLARE @cworkdays INT
DECLARE @cweekends INT
DECLARE @returnval INT
SET @workday_s = 510 -- work day start: 8.5 hours
SET @workday_e = 960 -- work day end: 16.0 hours
-- calculate Saturday and Sunday dependent on SET DATEFIRST option
SET @sat = CASE @@DATEFIRST WHEN 7 THEN 7 ELSE 7 - @@DATEFIRST END
SET @sun = CASE @@DATEFIRST WHEN 7 THEN 1 ELSE @sat + 1 END
SET @calcdate1 = @date1
SET @calcdate2 = @date2
-- @date1: assume next day if start was after end of workday
SET @basedate1 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate1))
SET @calcdate1 = CASE WHEN DATEDIFF(mi, @basedate1, @calcdate1) > @workday_e
THEN @basedate1 + 1
ELSE @calcdate1
END
-- @date1: if Saturday or Sunday, make it next Monday
SET @basedate1 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate1))
SET @calcdate1 = CASE DATEPART(dw, @basedate1)
WHEN @sat THEN @basedate1 + 2
WHEN @sun THEN @basedate1 + 1
ELSE @calcdate1
END
-- @date1: assume @workday_s as the minimum start time
SET @basedate1 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate1))
SET @calcdate1 = CASE WHEN DATEDIFF(mi, @basedate1, @calcdate1) < @workday_s
THEN DATEADD(mi, @workday_s, @basedate1)
ELSE @calcdate1
END
-- @date2: assume previous day if end was before start of workday
SET @basedate2 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate2))
SET @calcdate2 = CASE WHEN DATEDIFF(mi, @basedate2, @calcdate2) < @workday_s
THEN @basedate2 - 1
ELSE @calcdate2
END
-- @date2: if Saturday or Sunday, make it previous Friday
SET @basedate2 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate2))
SET @calcdate2 = CASE DATEPART(dw, @calcdate2)
WHEN @sat THEN @basedate2 - 0.00001
WHEN @sun THEN @basedate2 - 1.00001
ELSE @date2
END
-- @date2: assume @workday_e as the maximum end time
SET @basedate2 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate2))
SET @calcdate2 = CASE WHEN DATEDIFF(mi, @basedate2, @calcdate2) > @workday_e
THEN DATEADD(mi, @workday_e, @basedate2)
ELSE @calcdate2
END
-- count full work days (subtract Saturdays and Sundays)
SET @cworkdays = DATEDIFF(dd, @basedate1, @basedate2)
SET @cweekends = @cworkdays / 7
SET @cworkdays = @cworkdays - @cweekends * 2
-- calculate effective duration in minutes
SET @returnval = @cworkdays * (@workday_e - @workday_s)
+ @workday_e - DATEDIFF(mi, @basedate1, @calcdate1)
+ DATEDIFF(mi, @basedate2, @calcdate2) - @workday_e
-- return duration as an offset in minutes from date 0
RETURN DATEADD(mi, @returnval, 0)
END
Функция возвращает значение DATETIME
, означающее смещение от даты 0 (которая составляет "1900-01-01 00:00:00"
). Так, например, временной интервал в 8 часов будет "1900-01-01 08:00:00"
, а 25 часов - "1900-01-02 01:00:00"
. Результатом функции является разница во времени в рабочих часах между двумя датами. Никакой специальной обработки / поддержки сверхурочных.
SELECT dbo.udfDateDiffBusinessHours('2003-04-29 15:00:00', '2003-04-30 11:00:00')
--> 1900-01-01 03:30:00.000
SELECT dbo.udfDateDiffBusinessHours('2003-04-30 14:00:00', '2003-05-01 14:00:00')
--> 1900-01-01 07:30:00.000
Функция предполагает начало следующего доступного рабочего дня (08:30 ч), когда @date1
не в нерабочее время, и конец предыдущего доступного рабочего дня (16:00 ч), когда @date2
в нерабочее время.
«следующий / предыдущий доступный» означает:
- , если
@date1
равно '2009-02-06 07:00:00'
(пт), оно станет '2009-02-06 08:30:00'
(пт)
- если
@date1
равно '2009-02-06 19:00:00'
(пт), оно станет '2009-02-09 08:30:00'
(пн)
- если
@date2
равно '2009-02-09 07:00:00'
(понедельник), оно станет '2009-02-06 16:00:00'
(пт)
- если
@date2
равно '2009-02-09 19:00:00'
(понедельник), оно станет '2009-02-09 16:00:00'
(понедельник)