سؤال

أحتاج إلى حساب الدورات الضخمة (ساعات) بين تاريخين، ولكن فقط خلال ساعات العمل (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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top