SQL Datediff الاستخدام المتقدمة؟
سؤال
أحتاج إلى حساب الدورات الضخمة (ساعات) بين تاريخين، ولكن فقط خلال ساعات العمل (8:30 - 16:00، لا عطلة نهاية الأسبوع). ثم سيتم وضع هذه النتيجة في عمود Reaction_Time وفقا للمثال أدناه.
معرف التاريخ Reaction_Time OverDue 1 29.04.2003 15:00:00 1 30.04.2003 11:00 3:30 2 30.04.2003 14:00:00 2 01.05.2003 14:00:00 7:30 نعم
* ملاحظة: لم أتحقق من معرفة ما إذا كانت التواريخ في المثال كانت العطلات.
أنا أستخدم SQL Server 2005
سيتم دمج هذا مع استفسار أكبر، ولكن الآن كل ما أحتاج إليه هو أن نبدأ هذا، سأحاول معرفة كيفية وضع كل شيء معا بمفردي. شكرا للمساعدة!
يحرر: مهلا، شكرا للجميع على الردود. ولكن نظرا للتعقيد الواضح للحل على جانب SQL، فقد تقرر أن نفعل ذلك في Excel بدلا من ذلك، حيث سيتم نقل التقرير على أي حال. آسف للمشكلة، لكنني أحسب حقا أنها ستكون أبسط من هذا. كما هو، نحن فقط ليس لدينا الوقت.
المحلول
DECLARE @BusHourStart DATETIME, @BusHourEnd DATETIME
SELECT @BusHourStart = '08:30:00', @BusHourEnd = '16:00:00'
DECLARE @BusMinutesStart INT, @BusMinutesEnd INT
SELECT @BusMinutesStart = DATEPART(minute,@BusHourStart)+DATEPART(hour,@BusHourStart)*60,
@BusMinutesEnd = DATEPART(minute,@BusHourEnd)+DATEPART(hour,@BusHourEnd)*60
DECLARE @Dates2 TABLE (ID INT, DateStart DATETIME, DateEnd DATETIME)
INSERT INTO @Dates2
SELECT 1, '15:00:00 04/29/2003', '11:00:00 04/30/2003' UNION
SELECT 2, '14:00:00 04/30/2003', '14:00:00 05/01/2003' UNION
SELECT 3, '14:00:00 05/02/2003', '14:00:00 05/06/2003' UNION
SELECT 4, '14:00:00 05/02/2003', '14:00:00 05/04/2003' UNION
SELECT 5, '07:00:00 05/02/2003', '14:00:00 05/02/2003' UNION
SELECT 6, '14:00:00 05/02/2003', '23:00:00 05/02/2003' UNION
SELECT 7, '07:00:00 05/02/2003', '08:00:00 05/02/2003' UNION
SELECT 8, '22:00:00 05/02/2003', '23:00:00 05/03/2003' UNION
SELECT 9, '08:00:00 05/03/2003', '23:00:00 05/04/2003' UNION
SELECT 10, '07:00:00 05/02/2003', '23:00:00 05/02/2003'
-- SET DATEFIRST to U.S. English default value of 7.
SET DATEFIRST 7
SELECT ID, DateStart, DateEnd, CONVERT(VARCHAR, Minutes/60) +':'+ CONVERT(VARCHAR, Minutes % 60) AS ReactionTime
FROM (
SELECT ID, DateStart, DateEnd, Overtime,
CASE
WHEN DayDiff = 0 THEN
CASE
WHEN (MinutesEnd - MinutesStart - Overtime) > 0 THEN (MinutesEnd - MinutesStart - Overtime)
ELSE 0
END
WHEN DayDiff > 0 THEN
CASE
WHEN (StartPart + EndPart - Overtime) > 0 THEN (StartPart + EndPart - Overtime)
ELSE 0
END + DayPart
ELSE 0
END AS Minutes
FROM(
SELECT ID, DateStart, DateEnd, DayDiff, MinutesStart, MinutesEnd,
CASE WHEN(@BusMinutesStart - MinutesStart) > 0 THEN (@BusMinutesStart - MinutesStart) ELSE 0 END +
CASE WHEN(MinutesEnd - @BusMinutesEnd) > 0 THEN (MinutesEnd - @BusMinutesEnd) ELSE 0 END AS Overtime,
CASE WHEN(@BusMinutesEnd - MinutesStart) > 0 THEN (@BusMinutesEnd - MinutesStart) ELSE 0 END AS StartPart,
CASE WHEN(MinutesEnd - @BusMinutesStart) > 0 THEN (MinutesEnd - @BusMinutesStart) ELSE 0 END AS EndPart,
CASE WHEN DayDiff > 1 THEN (@BusMinutesEnd - @BusMinutesStart)*(DayDiff - 1) ELSE 0 END AS DayPart
FROM (
SELECT DATEDIFF(d,DateStart, DateEnd) AS DayDiff, ID, DateStart, DateEnd,
DATEPART(minute,DateStart)+DATEPART(hour,DateStart)*60 AS MinutesStart,
DATEPART(minute,DateEnd)+DATEPART(hour,DateEnd)*60 AS MinutesEnd
FROM (
SELECT ID,
CASE
WHEN DATEPART(dw, DateStart) = 7
THEN DATEADD(SECOND, 1, DATEADD(DAY, DATEDIFF(DAY, 0, DateStart), 2))
WHEN DATEPART(dw, DateStart) = 1
THEN DATEADD(SECOND, 1, DATEADD(DAY, DATEDIFF(DAY, 0, DateStart), 1))
ELSE DateStart END AS DateStart,
CASE
WHEN DATEPART(dw, DateEnd) = 7
THEN DATEADD(SECOND, -1, DATEADD(DAY, DATEDIFF(DAY, 0, DateEnd), 0))
WHEN DATEPART(dw, DateEnd) = 1
THEN DATEADD(SECOND, -1, DATEADD(DAY, DATEDIFF(DAY, 0, DateEnd), -1))
ELSE DateEnd END AS DateEnd FROM @Dates2
)Weekends
)InMinutes
)Overtime
)Calculation
نصائح أخرى
أود أن أوصي ببناء وظيفة محددة من قبل المستخدم التي تحسب اختلاف التاريخ في ساعات العمل وفقا لقواعدك.
SELECT
Id,
MIN(Date) DateStarted,
MAX(Date) DateCompleted,
dbo.udfDateDiffBusinessHours(MIN(Date), MAX(Date)) ReactionTime
FROM
Incident
GROUP BY
Id
لست متأكدا من أين Overdue
القيمة تأتي من، لذلك تركتها في مثالي.
في وظيفة يمكنك كتابة طريقة SQL أكثر تعبيرا منها في استفسار، ولا يمكنك تسجيل استفسارك مع قواعد العمل، مما يجعل من الصعب الحفاظ عليها.
كما يمكن إعادة استخدام وظيفة بسهولة. توسيعها لتشمل الدعم للعطلات (أنا أفكر في Holidays
الجدول هنا) لن يكون صعبا للغاية. مزيد من التحسينات ممكنة دون الحاجة إلى تغيير الصعب لقراءة / حالة المتداخلة عند البناء، والتي ستكون البديل.
إذا كان لدي وقت اليوم، فسوف ننظر في كتابة وظيفة مثال.
تحرير: هنا شيء مع أجراس وصفارات، حساب حوالي عطلة نهاية الأسبوع بشفافية:
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"
). لذلك على سبيل المثال، سيكون في 10:00 ساعات "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'
(الاثنين)
select datediff(hh,@date1,@date2) - 16.5*(datediff(dd,@date1,@date2))
الصيد الوحيد هو أنه سيعطيك 3:30 ك 3.5 ساعة ولكن يمكنك إصلاح ذلك بسهولة.
استخدم هذا الرمز: لمعرفة عطلة نهاية الأسبوع بين التواريخ
(
DATEDIFF(dd, open_date, zassignment_date) + 1
- ( (DATEDIFF(dd, open_date, zassignment_date) + 1)
-(DATEDIFF(wk, open_date, zassignment_date) * 2)
-(CASE WHEN DATENAME(dw, open_date) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, zassignment_date) = 'Saturday' THEN 1 ELSE 0 END) )) wk_end
على افتراض أن لديك جدول مرجعي أيام عمل (وساعاتهم)، ثم أود استخدام نهج 3 مراحل (Pseudo-SQL)
(أولا يمنع مثال تافهة "الكل في يوم واحد"، لأن هذا يبسط المنطق)
-- days that are neither the start nor end (full days)
SELECT @FullDayHours = SUM(day start to day end)
FROM reference-calendar
WHERE Start >= midnight-after-start and End <= midnight-before-end
-- time after the [query start] to the end of the first working day
SELECT @FirstDayHours = [query start] to day end
FROM reference-calandar
WHERE start day
-- time from the start of the last working day to the [query end]
SELECT @LastDayHours = day start to [query end]
FROM reference-calendar
WHERE end-day
IF @FirstDayHours < 0 SET @FirstDayHours = 0 -- starts outside working time
IF @LastDayHours < 0 SET @LastDayHours = 0 -- ends outside working time
PRINT @FirstDayHours + @FullDayHours + @LastDayHours
من الواضح أنه من الصعب بعض الشيء القيام به بشكل صحيح دون المزيد من السياق ...
هذه الوظيفة سوف تعطيك الفرق في ساعات العمل بين مرتين معينة. سيؤدي ذلك إلى إرجاع الفرق في دقائق أو ساعات بناء على المعلمة جزء التاريخ.
CREATE FUNCTION [dbo].[fnBusinessHoursDateDiff] (@StartTime SmallDatetime, @EndTime SmallDateTime, @DatePart varchar(2)) RETURNS DECIMAL (10,2)
AS
BEGIN
DECLARE @Minutes bigint
, @FinalNumber Decimal(10,2)
-- // Create Minute By minute table for CTE
-- ===========================================================
;WITH cteInputHours (StartTime, EndTime, NextTime) AS (
SELECT @StartTime
, @EndTime
, dateadd(mi, 1, @StartTime)
),
cteBusinessMinutes (TimeOfDay, [isBusHour], NextTime) AS(
SELECT StartTime [TimeOfDay]
, case when datepart(dw, StartTime) between 2 and 6 and convert(time,StartTime) between '08:30' and '15:59' then 1 else 0 end [isBusHour]
, dateadd(mi, 1, @StartTime) [NextTime]
FROM cteInputHours
UNION ALL
SELECT dateadd(mi, 1, (a.TimeOfDay)) [TimeOfDay]
, case when datepart(dw, a.TimeOfDay) between 2 and 6 and convert(time,dateadd(mi, 1, (a.TimeOfDay)) ) between '08:30' and '15:59' then 1 else 0 end [isBusHour]
, dateadd(mi, 2, (a.TimeOfDay)) NextTime
FROM cteBusinessMinutes a
WHERE dateadd(mi, 1, (a.TimeOfDay)) < @EndTime
)
SELECT @Minutes = count(*)
FROM cteBusinessMinutes
WHERE isBusHour = 1
OPTION (MAXRECURSION 0);
-- // Final Select
-- ===========================================================
SELECT @FinalNumber = @Minutes / (case when @DatePart = 'hh' then 60.00 else 1 end)
RETURN @FinalNumber
END