计算两个日期之间的营业时间

时间:2011-03-11 14:34:24

标签: sql sql-server datetime math

如何计算两个日期之间的营业时间? 例如,我们有两个日期; 01/01/2010 15:00和04/01/2010 12:00 我们工作时间的工作时间为09:00至17:00 如何用sql计算工作时间?

15 个答案:

答案 0 :(得分:26)

Baran的答案针对SQL 2005进行了修复和修改

SQL 2008及更高版本:

-- =============================================
-- Author:      Baran Kaynak (modified by Kodak 2012-04-18)
-- Create date: 14.03.2011
-- Description: 09:30 ile 17:30 arasındaki iş saatlerini hafta sonlarını almayarak toplar.
-- =============================================
CREATE FUNCTION [dbo].[WorkTime] 
(
    @StartDate DATETIME,
    @FinishDate DATETIME
)
RETURNS BIGINT
AS
BEGIN
    DECLARE @Temp BIGINT
    SET @Temp=0

    DECLARE @FirstDay DATE
    SET @FirstDay = CONVERT(DATE, @StartDate, 112)

    DECLARE @LastDay DATE
    SET @LastDay = CONVERT(DATE, @FinishDate, 112)

    DECLARE @StartTime TIME
    SET @StartTime = CONVERT(TIME, @StartDate)

    DECLARE @FinishTime TIME
    SET @FinishTime = CONVERT(TIME, @FinishDate)

    DECLARE @WorkStart TIME
    SET @WorkStart = '09:00'

    DECLARE @WorkFinish TIME
    SET @WorkFinish = '17:00'

    DECLARE @DailyWorkTime BIGINT
    SET @DailyWorkTime = DATEDIFF(MINUTE, @WorkStart, @WorkFinish)

    IF (@StartTime<@WorkStart)
    BEGIN
        SET @StartTime = @WorkStart
    END
    IF (@FinishTime>@WorkFinish)
    BEGIN
        SET @FinishTime=@WorkFinish
    END
    IF (@FinishTime<@WorkStart)
    BEGIN
        SET @FinishTime=@WorkStart
    END
    IF (@StartTime>@WorkFinish)
    BEGIN
        SET @StartTime = @WorkFinish
    END

    DECLARE @CurrentDate DATE
    SET @CurrentDate = @FirstDay
    DECLARE @LastDate DATE
    SET @LastDate = @LastDay

    WHILE(@CurrentDate<=@LastDate)
    BEGIN       
        IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7)
        BEGIN
            IF (@CurrentDate!=@FirstDay) AND (@CurrentDate!=@LastDay)
            BEGIN
                SET @Temp = @Temp + @DailyWorkTime
            END
            --IF it starts at startdate and it finishes not this date find diff between work finish and start as minutes
            ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate!=@LastDay)
            BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish)
            END

            ELSE IF (@CurrentDate!=@FirstDay) AND (@CurrentDate=@LastDay)
            BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime)
            END
            --IF it starts and finishes in the same date
            ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate=@LastDay)
            BEGIN
                SET @Temp = DATEDIFF(MINUTE, @StartTime, @FinishTime)
            END
        END
        SET @CurrentDate = DATEADD(day, 1, @CurrentDate)
    END

    -- Return the result of the function
    IF @Temp<0
    BEGIN
        SET @Temp=0
    END
    RETURN @Temp

END

SQL 2005及以下版本:

-- =============================================
-- Author:      Baran Kaynak (modified by Kodak 2012-04-18)
-- Create date: 14.03.2011
-- Description: 09:30 ile 17:30 arasındaki iş saatlerini hafta sonlarını almayarak toplar.
-- =============================================
CREATE FUNCTION [dbo].[WorkTime] 
(
    @StartDate DATETIME,
    @FinishDate DATETIME
)
RETURNS BIGINT
AS
BEGIN
    DECLARE @Temp BIGINT
    SET @Temp=0

    DECLARE @FirstDay DATETIME
    SET @FirstDay = DATEADD(dd, 0, DATEDIFF(dd, 0, @StartDate))

    DECLARE @LastDay DATETIME
    SET @LastDay = DATEADD(dd, 0, DATEDIFF(dd, 0, @FinishDate))

    DECLARE @StartTime DATETIME
    SET @StartTime = @StartDate - DATEADD(dd, DATEDIFF(dd, 0, @StartDate), 0)

    DECLARE @FinishTime DATETIME
    SET @FinishTime = @FinishDate - DATEADD(dd, DATEDIFF(dd, 0, @FinishDate), 0)

    DECLARE @WorkStart DATETIME
    SET @WorkStart = CONVERT(DATETIME, '09:00', 8)

    DECLARE @WorkFinish DATETIME
    SET @WorkFinish = CONVERT(DATETIME, '17:00', 8)

    DECLARE @DailyWorkTime BIGINT
    SET @DailyWorkTime = DATEDIFF(MINUTE, @WorkStart, @WorkFinish)

    IF (@StartTime<@WorkStart)
    BEGIN
        SET @StartTime = @WorkStart
    END
    IF (@FinishTime>@WorkFinish)
    BEGIN
        SET @FinishTime=@WorkFinish
    END
    IF (@FinishTime<@WorkStart)
    BEGIN
        SET @FinishTime=@WorkStart
    END
    IF (@StartTime>@WorkFinish)
    BEGIN
        SET @StartTime = @WorkFinish
    END

    DECLARE @CurrentDate DATETIME
    SET @CurrentDate = @FirstDay
    DECLARE @LastDate DATETIME
    SET @LastDate = @LastDay

    WHILE(@CurrentDate<=@LastDate)
    BEGIN       
        IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7)
        BEGIN
            IF (@CurrentDate!=@FirstDay) AND (@CurrentDate!=@LastDay)
            BEGIN
                SET @Temp = @Temp + @DailyWorkTime
            END
            --IF it starts at startdate and it finishes not this date find diff between work finish and start as minutes
            ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate!=@LastDay)
            BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish)
            END

            ELSE IF (@CurrentDate!=@FirstDay) AND (@CurrentDate=@LastDay)
            BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime)
            END
            --IF it starts and finishes in the same date
            ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate=@LastDay)
            BEGIN
                SET @Temp = DATEDIFF(MINUTE, @StartTime, @FinishTime)
            END
        END
        SET @CurrentDate = DATEADD(day, 1, @CurrentDate)
    END

    -- Return the result of the function
    IF @Temp<0
    BEGIN
        SET @Temp=0
    END
    RETURN @Temp

END

答案 1 :(得分:19)

我知道这篇帖子已经很老了,但这是我最近编写的一个函数来计算任意两个事件之间的营业时间/分钟。它还考虑了必须在表格中定义的任何假​​期。

该函数以分钟为单位返回间隔 - 您可以除以60以获得所需的小时数。

这已经在SQL Server 2008上进行了测试。希望它对某人有帮助。

Create Function GetWorkingMin(@StartDate DateTime, @EndDate DateTime, @Country Varchar(2)) Returns Int
AS
Begin
    Declare @WorkMin int = 0   -- Initialize counter
    Declare @Reverse bit       -- Flag to hold if direction is reverse
    Declare @StartHour int = 9   -- Start of business hours (can be supplied as an argument if needed)
    Declare @EndHour int = 17    -- End of business hours (can be supplied as an argument if needed)
    Declare @Holidays Table (HDate DateTime)   --  Table variable to hold holidayes

    -- If dates are in reverse order, switch them and set flag
    If @StartDate>@EndDate 
    Begin
        Declare @TempDate DateTime=@StartDate
        Set @StartDate=@EndDate
        Set @EndDate=@TempDate
        Set @Reverse=1
    End
    Else Set @Reverse = 0

    -- Get country holidays from table based on the country code (Feel free to remove this or modify as per your DB schema)
    Insert Into @Holidays (HDate) Select HDate from HOLIDAY Where COUNTRYCODE=@Country and HDATE>=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)

    If DatePart(HH, @StartDate)<@StartHour Set @StartDate = DateAdd(hour, @StartHour, DateDiff(DAY, 0, @StartDate))  -- If Start time is less than start hour, set it to start hour
    If DatePart(HH, @StartDate)>=@EndHour+1 Set @StartDate = DateAdd(hour, @StartHour+24, DateDiff(DAY, 0, @StartDate)) -- If Start time is after end hour, set it to start hour of next day
    If DatePart(HH, @EndDate)>=@EndHour+1 Set @EndDate = DateAdd(hour, @EndHour, DateDiff(DAY, 0, @EndDate)) -- If End time is after end hour, set it to end hour
    If DatePart(HH, @EndDate)<@StartHour Set @EndDate = DateAdd(hour, @EndHour-24, DateDiff(DAY, 0, @EndDate)) -- If End time is before start hour, set it to end hour of previous day

    If @StartDate>@EndDate Return 0

    -- If Start and End is on same day
    If DateDiff(Day,@StartDate,@EndDate) <= 0
    Begin
        If Datepart(dw,@StartDate)>1 And DATEPART(dw,@StartDate)<7  -- If day is between sunday and saturday
            If (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)) = 0  -- If day is not a holiday
                If @EndDate<@StartDate Return 0 Else Set @WorkMin=DATEDIFF(MI, @StartDate, @EndDate) -- Calculate difference
            Else Return 0
        Else Return 0
    End
    Else Begin
        Declare @Partial int=1   -- Set partial day flag
        While DateDiff(Day,@StartDate,@EndDate) > 0   -- While start and end days are different
        Begin
            If Datepart(dw,@StartDate)>1 And DATEPART(dw,@StartDate)<7    --  If this is a weekday
            Begin
                If (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)) = 0  -- If this is not a holiday
                Begin
                    If @Partial=1  -- If this is the first iteration, calculate partial time
                    Begin 
                        Set @WorkMin=@WorkMin + DATEDIFF(MI, @StartDate, DateAdd(hour, @EndHour, DateDiff(DAY, 0, @StartDate)))
                        Set @StartDate=DateAdd(hour, @StartHour+24, DateDiff(DAY, 0, @StartDate)) 
                        Set @Partial=0 
                    End
                    Else Begin      -- If this is a full day, add full minutes
                        Set @WorkMin=@WorkMin + (@EndHour-@StartHour)*60        
                        Set @StartDate = DATEADD(DD,1,@StartDate)
                    End
                End
                Else Set @StartDate = DATEADD(DD,1,@StartDate)  
            End
            Else Set @StartDate = DATEADD(DD,1,@StartDate)
        End
        If Datepart(dw,@StartDate)>1 And DATEPART(dw,@StartDate)<7  -- If last day is a weekday
            If (Select Count(*) From @Holidays Where HDATE=DateAdd(dd, DateDiff(dd,0,@StartDate), 0)) = 0   -- And it is not a holiday
                If @Partial=0 Set @WorkMin=@WorkMin + DATEDIFF(MI, @StartDate, @EndDate) Else Set @WorkMin=@WorkMin + DATEDIFF(MI, DateAdd(hour, @StartHour, DateDiff(DAY, 0, @StartDate)), @EndDate)
    End 
    If @Reverse=1 Set @WorkMin=-@WorkMin
    Return @WorkMin
End

答案 2 :(得分:8)

第一步是计算工作日,如下面的脚本所示:

DECLARE @TotalWorkDays INT, @TotalTimeDiff DECIMAL(18, 2), @DateFrom DATETIME, @DateTo DATETIME;
SET @DateFrom = '2017-06-05 11:19:11.287';
SET @DateTo = '2017-06-07 09:53:14.750';

SET @TotalWorkDays = DATEDIFF(DAY, @DateFrom, @DateTo)
    -(DATEDIFF(WEEK, @DateFrom, @DateTo) * 2)
   -CASE
                                    WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
                                    THEN 1
                                    ELSE 0
                                END+CASE
                                        WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
                                        THEN 1
                                        ELSE 0
                                    END;

第二步涉及在两个日期之间获得秒数的差异,并将该差异转换为小时数除以3600.0,如下面的脚本所示:

SET @TotalTimeDiff =
(
    SELECT DATEDIFF(SECOND,
                   (
                       SELECT CONVERT(TIME, @DateFrom)
                   ),
                   (
                       SELECT CONVERT(TIME, @DateTo)
                   )) / 3600.0
);

最后一部分涉及将上面第一步的输出乘以24(一天中的总小时数),然后将其添加到第二步的输出中:

SELECT(@TotalWorkDays * 24.00) + @TotalTimeDiff;

最后,可用于创建用户定义函数以计算工作时间的完整脚本如下所示:

CREATE FUNCTION [dbo].[fn_GetTotalWorkingHours]
(
    @DateFrom Datetime,
    @DateTo Datetime
)
RETURNS DECIMAL(18,2)
AS
BEGIN

DECLARE @TotalWorkDays INT, @TotalTimeDiff DECIMAL(18, 2)

SET @TotalWorkDays = DATEDIFF(DAY, @DateFrom, @DateTo)
    -(DATEDIFF(WEEK, @DateFrom, @DateTo) * 2)
   -CASE
                                    WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
                                    THEN 1
                                    ELSE 0
                                END+CASE
                                        WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
                                        THEN 1
                                        ELSE 0
                                    END;
SET @TotalTimeDiff =
(
    SELECT DATEDIFF(SECOND,
                   (
                       SELECT CONVERT(TIME, @DateFrom)
                   ),
                   (
                       SELECT CONVERT(TIME, @DateTo)
                   )) / 3600.0
);

RETURN  (SELECT(@TotalWorkDays * 24.00) + @TotalTimeDiff)

END
GO

本文描述了完整的方法: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

答案 3 :(得分:7)

DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
DECLARE @WORKINGHOURS INT
DECLARE @Days INT
SET @StartDate = '2010/01/01'
SET @EndDate = '2010/04/01'

--number of working days
SELECT @Days = 
   (DATEDIFF(dd, @StartDate, @EndDate) + 1)
  -(DATEDIFF(wk, @StartDate, @EndDate) * 2)
  -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END)
  -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)

--8 hours a day    
SET @WORKINGHOURS = @Days * 8 

SELECT @WORKINGHOURS

答案 4 :(得分:7)

来自@ Pavanred的替代解决方案,从更基于数据的角度出发:

创建一个包含您要在其中考虑的所有日期的表。对于每一天,设置一些工作小时,如下:

WorkingDate Hours Comment
=========== ===== ==================
 1 Jan 2011     0 Saturday
 2 Jan 2011     0 Sunday
 3 Jan 2011     0 Public Holiday
 4 Jan 2011     8 Normal working day
 5 Jan 2011     8 Normal working day

 -- and so on, for all the days you want to report on.

这需要进行少量设置 - 您可以自动将其预先填充数周而不是周末,然后根据需要调整公众假期等。

但是,你在设置中失去了什么,你很容易获得查询:

SELECT
  SUM(Hours) 
FROM
  working_days 
WHERE
  WorkingDate BETWEEN @StartDate AND @EndDate

...如果您需要开始为定义工作日的内容添加更复杂的规则,或者如果您的工作时间因日期而异,等等,这可以作为一种更简单的方法。

它还使规则更容易“可编辑”,因为您无需更改任何实际代码来更改工作日的定义,添加公共假期等。

答案 5 :(得分:5)

    -- =============================================
-- Author:      Baran Kaynak
-- Create date: 14.03.2011
-- Description: 09:30 ile 17:30 arasındaki iş saatlerini hafta sonlarını almayarak toplar.
-- =============================================
CREATE FUNCTION [dbo].[WorkTime] 
(
    @StartDate DATETIME,
    @FinishDate DATETIME
)
RETURNS BIGINT
AS
BEGIN
    DECLARE @Temp BIGINT
    SET @Temp=0

    DECLARE @FirstDay DATE
    SET @FirstDay = CONVERT(DATE, @StartDate, 112)

    DECLARE @LastDay DATE
    SET @LastDay = CONVERT(DATE, @FinishDate, 112)

    DECLARE @StartTime TIME
    SET @StartTime = CONVERT(TIME, @StartDate)

    DECLARE @FinishTime TIME
    SET @FinishTime = CONVERT(TIME, @FinishDate)

    DECLARE @WorkStart TIME
    SET @WorkStart = '09:30'

    DECLARE @WorkFinish TIME
    SET @WorkFinish = '17:30'

    IF (@StartTime<@WorkStart)
    BEGIN
        SET @StartTime = @WorkStart
    END
    IF (@FinishTime>@WorkFinish)
    BEGIN
        SET @FinishTime=@WorkFinish
    END

    DECLARE @CurrentDate DATE
    SET @CurrentDate = CONVERT(DATE, @StartDate, 112)
    DECLARE @LastDate DATE
    SET @LastDate = CONVERT(DATE, @FinishDate, 112)

    WHILE(@CurrentDate<=@LastDate)
    BEGIN       
        IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7)
        BEGIN
            IF (@CurrentDate!=@FirstDay) AND (@CurrentDate!=@LastDay)
            BEGIN
                SET @Temp = (@Temp + (9*60))
            END
            --IF it starts at startdate and it finishes not this date find diff between work finish and start as minutes
            ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate!=@LastDay)
            BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish)
            END

            ELSE IF (@CurrentDate!=@FirstDay) AND (@CurrentDate=@LastDay)
            BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime)
            END
            --IF it starts and finishes in the same date
            ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate=@LastDay)
            BEGIN
                SET @Temp = DATEDIFF(MINUTE, @StartDate, @FinishDate)
            END
        END
        SET @CurrentDate = DATEADD(day, 1, @CurrentDate)
    END

    -- Return the result of the function
    IF @Temp<0
    BEGIN
        SET @Temp=0
    END
    RETURN @Temp

END

GO

答案 6 :(得分:2)

ALTER FUNCTION WorkTime_fn (@StartDate DATETIME, @FinishDate DATETIME)
RETURNS VARCHAR(9)
AS
BEGIN
    DECLARE @Temp BIGINT
    SET @Temp=0

    DECLARE @FirstDay VARCHAR(9)
    SET @FirstDay = CONVERT(VARCHAR(9),@StartDate, 112)

    DECLARE @LastDay VARCHAR(9)
    SET @LastDay = CONVERT(VARCHAR(9),@FinishDate, 112)

    DECLARE @StartTime VARCHAR(9)
    SET @StartTime = CONVERT(VARCHAR(9),@StartDate, 108)

    DECLARE @FinishTime VARCHAR(9)
    SET @FinishTime = CONVERT(VARCHAR(9),@FinishDate, 108)

    DECLARE @WorkStart VARCHAR(9)
    SET @WorkStart = '09:30:00'

    DECLARE @WorkFinish VARCHAR(9)
    SET @WorkFinish = '17:30:00'

    IF (@StartTime<@WorkStart)
    BEGIN
        SET @StartTime = @WorkStart
    END
    IF (@FinishTime>@WorkFinish)
    BEGIN
        SET @FinishTime=@WorkFinish
    END

DECLARE @CurrentDate VARCHAR(9)
    SET @CurrentDate = CONVERT(VARCHAR(9),@StartDate, 112)
    DECLARE @LastDate VARCHAR(9)
    SET @LastDate = CONVERT(VARCHAR(9),@FinishDate, 112)

WHILE(@CurrentDate<=@LastDate)
BEGIN       

        IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7)
        BEGIN
              IF (@CurrentDate!=@FirstDay) AND (@CurrentDate!=@LastDay)
              BEGIN
                   SET @Temp = (@Temp + (8*60))

              END

              ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate!=@LastDay)
              BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish)

              END

              ELSE IF (@CurrentDate!=@FirstDay) AND (@CurrentDate=@LastDay)
              BEGIN
                SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime)

              END

              ELSE IF (@CurrentDate=@FirstDay) AND (@CurrentDate=@LastDay)
              BEGIN
                SET @Temp = DATEDIFF(MINUTE, @StartTime, @FinishTime)

              END

             END

SET @CurrentDate = CONVERT(VARCHAR(9),DATEADD(day, 1, @CurrentDate),112)

END
        Return @TEMP

END

答案 7 :(得分:1)

这是一个内联版本 Start / EndDateTime如2015-03-16 09:52:24.000 开始/结束时间(营业时间),如07:00:00 它很笨重但可以在你的选择声明中使用

我也会在功能版中发布它。

Case when  <StartDate>= <EndDate> then 0
    When Convert(date,<StartDate>) = Convert(date,<EndDate>) Then 
         IIF( DATEPART(Dw,<StartDate>)  in(1,7)
                  or Convert(time,<StartDate>) > Convert(time,<EndTime>)
                  or Convert(time,<EndDate>) < Convert(time,<StartTime>),0, 
        DateDiff(S,IIF(Convert(time,<StartDate>) < Convert(time,<StartTime>),Convert(time,<StartTime>),Convert(time,<StartDate>))
                ,IIF(Convert(time,<EndDate>) > Convert(time,<EndTime>), Convert(time,<EndTime>), Convert(time,<EndDate>))))
    when  Convert(date,<StartDate>) <> Convert(date,<EndDate>) then 
        IIF(DATEPART(Dw,<StartDate>) in(1,7) or Convert(time,<StartDate>) >  Convert(time,<EndTime>),0 ,DateDiff(S,IIF(Convert(time,<StartDate>) < Convert(time,<StartTime>),Convert(time,<StartTime>),Convert(time,<StartDate>)), Convert(time,<EndTime>)))
        + IIF(DATEPART(Dw,<EndDate>) in(1,7) or  Convert(time,<EndDate>) <  Convert(time,<StartTime>),0,DateDiff(S,Convert(time,<StartTime>),IIF(Convert(time,<EndDate>) > Convert(time,<EndTime>), Convert(time,<EndTime>), Convert(time,<EndDate>))))
    else -333
    end --as pday

+IIF(DatePart(wEEk,<StartDate>)  = DatePart(wEEk,<EndDate>) 
,0, (DateDiff(wk,dateadd(d,-datepart(dw,<StartDate>),dateadd(ww,1,<StartDate>)),DATEADD(wk, DATEDIFF(wk, 6, <EndDate>), 6)-1) * 5)) * Datediff(S, Convert(time,<StartTime>),Convert(time,<EndTime>)) --Fullweek_days

+Case When Convert(date,<StartDate>) = Convert(date,<EndDate>) then 0
      When DatePart(wEEk,<StartDate>)  <> DatePart(wEEk,<EndDate>) then
                        IIF( datepart(dw,<StartDate>) = 7,0,DateDIFF(DAY,<StartDate>+1,dateadd(d,-datepart(dw,<StartDate>),dateadd(ww,1,<StartDate>))))  -- beginFulldays
                        +IIF( datepart(dw,<EndDate>) = 1,0,DateDIFF(DAY,DATEADD(wk, DATEDIFF(wk, 6, <EndDate>), 6),<EndDate> -1))  --Endfulldays
      When DatePart(wEEk,<StartDate>)  = DatePart(wEEk,<EndDate>) then
            DateDiff(DAY,<StartDate>+1,<EndDate> ) 
    ELSE -333 END * Datediff(S, Convert(time,<StartTime>),Convert(time,<EndTime>))    

这是功能版本:

CREATE FUNCTION [dbo].[rsf_BusinessTime]
(
@startDateTime Datetime,
@endDateTime Datetime ,
@StartTime VarChar(12),
@EndTime VarChar(12) )
RETURNS BIGINT
As
BEGIN
Declare @totalSeconds BigInt,
    @SecondsInDay int,
    @dayStart Time = Convert(time,@StartTime),
    @dayEnd Time =Convert(time,@EndTime),
    @SatAfterStart Datetime = dateadd(d,-datepart(dw,@startDateTime),dateadd(ww,1,@startDateTime)), 
    @Sunbeforend Datetime = DATEADD(wk, DATEDIFF(wk, 6, @endDateTime), 6) 

-- This function calculates the seconds between the start and end dates provided for business hours. 
-- It only returns the time between the @start and @end time (hour of day) of the work week. 
-- Weekend days are removed.
-- Holidays are not considered.  

Set @SecondsInDay = Datediff(S, @dayStart,@dayEnd) 


Set @totalSeconds = 
 --first/last/sameday
    Case when  @startDateTime= @endDateTime then 0
    When Convert(date,@startDateTime) = Convert(date,@endDateTime) Then 
         IIF( DATEPART(Dw,@startDateTime)  in(1,7)
                  or Convert(time,@startDateTime) > @dayEnd
                  or Convert(time,@endDateTime) < @dayStart,0, 
        DateDiff(S,IIF(Convert(time,@startDateTime) < @dayStart,@dayStart,Convert(time,@startDateTime))
                ,IIF(Convert(time,@endDateTime) > @dayEnd, @dayEnd, Convert(time,@endDateTime))))
    when  Convert(date,@startDateTime) <> Convert(date,@endDateTime) then 
        IIF(DATEPART(Dw,@startDateTime) in(1,7) or Convert(time,@startDateTime) >  @dayEnd,0 ,DateDiff(S,IIF(Convert(time,@startDateTime) < @dayStart,@dayStart,Convert(time,@startDateTime)), @dayEnd))
        + IIF(DATEPART(Dw,@endDateTime) in(1,7) or  Convert(time,@endDateTime) <  @dayStart,0,DateDiff(S,@dayStart,IIF(Convert(time,@endDateTime) > @dayEnd, @dayEnd, Convert(time,@endDateTime))))
    else -333
    end --as pday

+IIF(DatePart(wEEk,@startDateTime)  = DatePart(wEEk,@endDateTime)   
,0, (DateDiff(wk,@SatAfterStart,@Sunbeforend-1) * 5)) * @SecondsInDay --Fullweek_days

+Case When Convert(date,@startDateTime) = Convert(date,@endDateTime) then 0
      When DatePart(wEEk,@startDateTime)  <> DatePart(wEEk,@endDateTime) then
                        IIF( datepart(dw,@startDateTime) = 7,0,DateDIFF(DAY,@startDateTime+1,@SatAfterStart))  -- beginFulldays
                        +IIF( datepart(dw,@endDateTime) = 1,0,DateDIFF(DAY,@Sunbeforend,@endDateTime -1))  --Endfulldays
      When DatePart(wEEk,@startDateTime)  = DatePart(wEEk,@endDateTime) then
            DateDiff(DAY,@startDateTime+1,@endDateTime ) 
    ELSE -333 END * @SecondsInDay


Return @totalSeconds
END 

答案 8 :(得分:1)

问题是不应该考虑公众假期,所以这个答案就是这样 - 计算考虑周末的营业时间,但忽略可能的公众假期。

它还假设给定的开始和结束日期/时间是在营业时间内。

通过这种假设,代码并不关心工作日开始或结束的时间,它只关心每天的工作时间总数。在您的示例中,09:00至17:00之间有8个工作小时。它不一定是一个整数。下面的公式以一分钟的精度计算它,但是使它成为一秒或任何其他精度是微不足道的。

如果您需要考虑公共假期,您需要有一个单独的表格,列出公共假期的日期,这些日期可能因年度和州与州或国家而异。主要公式可能保持不变,但您需要从公布假期的结果小时数中减去指定的日期范围。

公式

SELECT
    DATEDIFF(minute, StartDT, EndDT) / 60.0
    - DATEDIFF(day,  StartDT, EndDT) * 16
    - DATEDIFF(week, StartDT, EndDT) * 16 AS BusinessHours
FROM T

要了解它的工作原理,让我们创建一个表格,其中包含一些涵盖各种情况的样本数据:

DECLARE @T TABLE (StartDT datetime2(0), EndDT datetime2(0));

INSERT INTO @T VALUES
('2012-03-05 09:00:00', '2012-03-05 15:00:00'), -- simple part of the same day
('2012-03-05 10:00:00', '2012-03-06 10:00:00'), -- full day across the midnight
('2012-03-05 11:00:00', '2012-03-06 10:00:00'), -- less than a day across the midnight
('2012-03-05 10:00:00', '2012-03-06 15:00:00'), -- more than a day across the midnight
('2012-03-09 16:00:00', '2012-03-12 10:00:00'), -- over the weekend, less than 7 days
('2012-03-06 16:00:00', '2012-03-15 10:00:00'), -- over the weekend, more than 7 days
('2012-03-09 16:00:00', '2012-03-19 10:00:00'); -- over two weekends

查询

SELECT
    StartDT, 
    EndDT,
    DATEDIFF(minute, StartDT, EndDT) / 60.0
    - DATEDIFF(day,  StartDT, EndDT) * 16
    - DATEDIFF(week, StartDT, EndDT) * 16 AS BusinessHours
FROM @T;

产生以下结果:

+---------------------+---------------------+---------------+
|       StartDT       |        EndDT        | BusinessHours |
+---------------------+---------------------+---------------+
| 2012-03-05 09:00:00 | 2012-03-05 15:00:00 |  6.000000     |
| 2012-03-05 10:00:00 | 2012-03-06 10:00:00 |  8.000000     |
| 2012-03-05 11:00:00 | 2012-03-06 10:00:00 |  7.000000     |
| 2012-03-05 10:00:00 | 2012-03-06 15:00:00 | 13.000000     |
| 2012-03-09 16:00:00 | 2012-03-12 10:00:00 |  2.000000     |
| 2012-03-06 16:00:00 | 2012-03-15 10:00:00 | 50.000000     |
| 2012-03-09 16:00:00 | 2012-03-19 10:00:00 | 42.000000     |
+---------------------+---------------------+---------------+

它有效,因为在SQL Server DATEDIFF中返回指定的 startdate enddate datepart 边界的计数>

每天有8个工作小时。我计算两个日期之间的总小时数,然后减去中午的数量乘以每天16个非营业时间,然后减去周末的数量乘以16(周六+周日的8 + 8个工作小时)。

答案 9 :(得分:0)

我之前实际上已经做过这个,考虑到所有变量(周末,假期等)的营业时间非常困难,我认为这个任务最好在SQL之外完成

答案 10 :(得分:0)

另一种思维方式,如果您的星期一是星期一,则以下功能正常工作,否则您应该将相关的行(包括(6,7))更改为当地周末的日子

create function fn_worktime(@Datetime1 DateTime,@Datetime2 DateTime)
Returns BigInt
as
Begin
    Declare 
            @Date1 Date, 
            @Date2 Date,
            @DateIndex Date,
            @minutes int,
            @lastDayMinutes int,
            @StartTime int , --in minutes
            @FinishTime int ,--in minutes
            @WorkDayLong int --in minutes

    Set @StartTime  =8 * 60 + 30 -- 8:30
    Set @FinishTime =17* 60 + 30 -- 17:30
    Set @WorkDayLong =@FinishTime - @StartTime  

    Set @Date1 = Convert(Date,@DateTime1)
    Set @Date2 = Convert(Date,@DateTime2)
    Set @minutes=DateDiff(minute,@DateTime1,DateAdd(MINUTE,@FinishTime ,convert(DateTime,@Date1)))
    if @minutes<0 OR DatePart(dw,@Date1) in (6,7)  -- you can even check holdays here. '(6 Saturday,7 Sunday) according to SET DATEFIRST 1'
        Set @minutes=0

    Set @DateIndex=DateAdd(day,1,@Date1)
    While @DateIndex<@Date2
    Begin
        if DatePart(dw,@DateIndex) not in (6,7)  -- you can even check holdays here. '(6 Saturday,7 Sunday) according to SET DATEFIRST 1'
            set @minutes=@minutes+@WorkDayLong 
        Set @DateIndex=DateAdd(day,1,@DateIndex)
    End
    if DatePart(dw,@DateIndex) not in (6,7)  -- you can even check holdays here
    Begin
        set @lastDayMinutes=DateDiff(minute,DateAdd(MINUTE ,@StartTime ,convert(DateTime,@Date2)),@DateTime2)
        if @lastDayMinutes>@WorkDayLong 
            set @lastDayMinutes=@WorkDayLong 
        if @Date1<>@Date2   
            set @minutes=@minutes+@lastDayMinutes
        Else
            Set @minutes=@minutes+@lastDayMinutes-@WorkDayLong 

    End
    return @minutes
End

答案 11 :(得分:0)

您对此解决方案有何看法?

不使用循环&#34;而&#34;。

create function dbo.WorkingHoursBetweenDates ( @StartDate datetime, @EndDate datetime, @StartTime time, @EndTime time )
returns decimal ( 10, 2 )
as
begin

  return
    case
      when @EndTime < @StartTime or @EndDate < @StartDate then
        0
      else
        round
        ( ( dbo.WorkingDaysBetweenDates(@StartDate, @EndDate) -
            ( dbo.WorkingDaysBetweenDates(@StartDate, @StartDate) *
              case
                when cast ( @StartDate as time ) > @EndTime then
                  1
                else
                  datediff
                  ( mi,
                    @StartTime
                    , case
                        when @StartTime > cast ( @StartDate as time ) then
                          @StartTime
                        else
                          cast ( @StartDate as time )
                      end
                  ) /
                  ( datediff ( mi, @StartTime, @EndTime ) + 0.0 )
              end
            ) -
            ( dbo.WorkingDaysBetweenDates(@EndDate, @EndDate) *
              case
                when cast ( @EndDate as time ) < @StartTime then
                  1
                else
                  datediff
                  ( mi,
                    case
                      when @EndTime < cast ( @EndDate as time ) then
                        @EndTime
                      else
                        cast ( @EndDate as time )
                    end,
                    @EndTime
                  ) /
                  ( datediff ( mi, @StartTime, @EndTime ) + 0.0 )
              end
            )
          ) *
          ( datediff ( mi, @StartTime, @EndTime ) / 60.0 ), 2
        )
    end

end
------

create function dbo.WorkingDaysBetweenDates ( @StartDate date, @EndDate date )
returns int
as
begin

  return 
    ( datediff(dd, @StartDate, @EndDate) + 1 ) -
    ( datediff(wk, @StartDate, @EndDate) * 2 ) -
    ( case when datename(dw, @StartDate) = 'Sunday' then 1 else 0 end ) -
    ( case when datename(dw, @EndDate) = 'Saturday' then 1 else 0 end ) -
    ( select
        count ( 1 )
      from
        dbo.Tb_Holidays
      where
        HDate between @StartDate and @EndDate
        and datename(dw, HDate) not in ( 'Sunday', 'Saturday' )
    )

end

答案 12 :(得分:0)

这是一种替代解决方案,不使用功能。请注意,这取决于是否存在numbers table,其中至少填充了您要跟踪的任务可能需要的最长天数。

这不考虑公众假期。如果你不在周末工作,在@OpeningHours表变量中将开始和结束时间设置为午夜应该可以胜任。

我已经对8500行“现实世界”进行了测试。数据,发现它是高效的。

DECLARE @OpeningHours TABLE ([DayOfWeek] INTEGER, OpeningTime TIME(0), ClosingTime TIME(0));

INSERT
    @OpeningHours ([DayOfWeek], OpeningTime, ClosingTime)
VALUES
    (1, '10:00', '16:00') -- Sun
    , (2, '06:30', '23:00') -- Mon
    , (3, '06:30', '23:00') -- Tue
    , (4, '06:30', '23:00') -- Wed
    , (5, '06:30', '23:00') -- Thu
    , (6, '06:30', '23:00') -- Fri
    , (7, '08:00', '20:00'); -- Sat

DECLARE @Tasks TABLE ([Description] VARCHAR(50), CreatedDateTime DATETIME, CompletedDateTime DATETIME);

INSERT
    @Tasks ([Description], CreatedDateTime, CompletedDateTime)
VALUES
    ('Make tea', '20170404 10:00', '20170404 10:12')
    , ('Make coffee', '20170404 23:35', '20170405 06:32')
    , ('Write complex SQL query', '20170406 00:00', '20170406 23:32')
    , ('Rewrite complex SQL query', '20170406 23:50', '20170410 10:50');

SELECT
    WorkingMinutesToRespond =
        SUM(CASE WHEN CAST(Tasks.CreatedDateTime AS DATE) = CAST(Tasks.CompletedDateTime AS DATE) THEN
        CASE WHEN CAST(Tasks.CreatedDateTime AS TIME) < OpeningHours.OpeningTime THEN
            -- Task created before opening time
            DATEDIFF(MINUTE, OpeningHours.OpeningTime, CAST(Tasks.CompletedDateTime AS TIME))
        ELSE
            DATEDIFF(MINUTE, Tasks.CreatedDateTime, Tasks.CompletedDateTime)
        END
    ELSE
        CASE WHEN Tasks.CoveredDate = CAST(Tasks.CreatedDateTime AS DATE) THEN 
            -- This is the day the task was created
            CASE WHEN CAST(Tasks.CreatedDateTime AS TIME(0)) > OpeningHours.ClosingTime THEN
                0 -- after working hours
            ELSE
                -- during or before working hours
                CASE WHEN CAST(Tasks.CreatedDateTime AS TIME(0)) < OpeningHours.OpeningTime THEN
                    -- before opening time; take the whole day into account
                    DATEDIFF(MINUTE, OpeningHours.OpeningTime, OpeningHours.ClosingTime)
                ELSE
                    -- during opening hours; take part of the day into account
                    DATEDIFF(MINUTE, CAST(Tasks.CreatedDateTime AS TIME), OpeningHours.ClosingTime)
                END
            END
        ELSE
            -- This is the day the task was completed
            CASE WHEN Tasks.CoveredDate = CAST(Tasks.CompletedDateTime AS DATE) THEN 
                CASE WHEN CAST(Tasks.CompletedDateTime AS TIME(0)) < OpeningHours.OpeningTime THEN
                    0 -- before working hours (unlikely to occur)
                ELSE
                    -- during or after working hours
                    CASE WHEN CAST(Tasks.CompletedDateTime AS TIME(0)) > OpeningHours.ClosingTime THEN
                        -- after closing time (also unlikely); take the whole day into account
                        DATEDIFF(MINUTE, OpeningHours.OpeningTime, OpeningHours.ClosingTime)
                    ELSE
                        -- during opening hours; take part of the day into account
                        DATEDIFF(MINUTE, OpeningHours.OpeningTime, CAST(Tasks.CompletedDateTime AS TIME(0)))
                    END
                END
        ELSE
            DATEDIFF(MINUTE, OpeningHours.OpeningTime, OpeningHours.ClosingTime)
        END 
        END
    END)
    , Tasks.Description
    , Tasks.CreatedDateTime
    , Tasks.CompletedDateTime
FROM
    (
        SELECT
        Tasks.Description
        , Tasks.CreatedDateTime
        , Tasks.CompletedDateTime
        , CoveredDate = CAST(DATEADD(DAY, Numbers.Number, Tasks.CreatedDateTime) AS DATE)
    FROM
        @Tasks Tasks
        INNER JOIN (SELECT * FROM Numbers WHERE Number >= 0) Numbers ON DATEDIFF(DAY, Tasks.CreatedDateTime, Tasks.CompletedDateTime) >= Numbers.Number
) Tasks
INNER JOIN @OpeningHours OpeningHours ON DATEPART(WEEKDAY, Tasks.CoveredDate) = OpeningHours.[DayOfWeek]
GROUP BY
    Tasks.Description
    , Tasks.CreatedDateTime
    , Tasks.CompletedDateTime
ORDER BY
    Tasks.CompletedDateTime;

答案 13 :(得分:0)

我今天写的考虑银行假期的版本。注意:此方法尚未经过全面测试,无疑会得到改进。

CREATE FUNCTION [dbo].[WorkingHoursBetween2Dates]
(
    @dtFrom datetime,
    @dtTo datetime
)
RETURNS INT
BEGIN
    DECLARE @tblDates AS TABLE (DateValue DATE)
    DECLARE @dFrom date = @dtFrom
    DECLARE @dTo date = @dtTo
    DECLARE @intDays int
    DECLARE @intHours int = 0
    DECLARE @dFromWorkday bit = CASE WHEN (DATENAME(WEEKDAY, @dFrom) IN ('Saturday','Sunday')) OR EXISTS (SELECT * FROM dbo.BankHolidays WHERE BankHolidayDate = @dFrom) THEN 0 ELSE 1 END
    DECLARE @dToWorkday bit = CASE WHEN (DATENAME(WEEKDAY, @dTo) IN ('Saturday','Sunday')) OR EXISTS (SELECT * FROM dbo.BankHolidays WHERE BankHolidayDate = @dTo) THEN 0 ELSE 1 END



    IF DATEPART(HOUR,@dtFrom) < 9
        SET @dtFrom = DATEADD(HOUR,9,CAST(CAST(@dtFrom AS DATE) AS DATETIME))
    ELSE
        IF DATEPART(HOUR,@dtFrom) > 17
            SET @dtFrom = DATEADD(HOUR,17,CAST(CAST(@dtFrom AS DATE) AS DATETIME))

    IF DATEPART(HOUR,@dtTo) < 9
        SET @dtTo = DATEADD(HOUR,9,CAST(CAST(@dtTo AS DATE) AS DATETIME))
    ELSE
        IF DATEPART(HOUR,@dtTo) > 17
            SET @dtTo = DATEADD(HOUR,17,CAST(CAST(@dtTo AS DATE) AS DATETIME))



    WHILE @dFrom <= @dTo
    BEGIN
        INSERT INTO @tblDates
        (
            DateValue
        )
            SELECT @dFrom
            WHERE NOT ((DATENAME(WEEKDAY, @dFrom) IN ('Saturday','Sunday')) OR EXISTS (SELECT * FROM dbo.BankHolidays WHERE BankHolidayDate = @dFrom))
        
        SET @dFrom = DATEADD(DAY,1,@dFrom)
    END
    
    SET @intDays = CASE WHEN EXISTS(SELECT * FROM @tblDates) THEN (SELECT COUNT(*) FROM @tblDates) - 1 ELSE 0 END

    IF @intDays = 0
        BEGIN
            IF @dFromWorkday = 1
                IF DATEPART(HOUR,@dtFrom) < 17
                    BEGIN
                        IF DATEDIFF(DAY,@dtFrom,@dtTo)=0
                            SET @intHours = DATEDIFF(HOUR,@dtFrom,@dtTo)
                        ELSE
                            SET @intHours = DATEDIFF(HOUR,@dtFrom,DATEADD(HOUR,17,CAST(CAST(@dtFrom AS DATE) AS DATETIME)))
                    END

            IF @dToWorkday = 1 AND DATEDIFF(DAY,@dtFrom,@dtTo)<>0
                IF DATEPART(HOUR,@dtTo) >= 17
                    SET @intHours = @intHours + 8
                ELSE
                    IF DATEPART(HOUR,@dtTo) > 9
                        SET @intHours = @intHours + DATEPART(HOUR,@dtTo) - 9
        END
    ELSE
        BEGIN
            IF @dFromWorkday = 1
                IF DATEPART(HOUR,@dtFrom) < 17
                    BEGIN
                        SET @intHours = DATEDIFF(HOUR,@dtFrom,DATEADD(HOUR,17,CAST(CAST(@dtFrom AS DATE) AS DATETIME)))
                        SET @intDays = @intDays - 1
                    END

            IF DATEPART(HOUR,@dtTo) < 17
                SET @intHours = @intHours + (@intDays * 8) + CASE WHEN @dToWorkday = 1 THEN DATEDIFF(HOUR,DATEADD(HOUR,9,CAST(CAST(@dtTo AS DATE) AS DATETIME)), @dtTo) ELSE 0 END
            ELSE
                SET @intHours = @intHours + ((@intDays + 1) * 8)
        END

    RETURN (@intHours)
END

答案 14 :(得分:0)

我刚刚遇到了这个问题,并提出了以下解决方案。它将作为工作日的开始和结束时间,计算出部分工作日使用的分钟数,如果工作时间超过 x 小时,则删除午休时间,并删除周末和银行假期。

这是锻炼: 日期从“2020-09-09 07:49”到“2020-12-18 08:27:00.000” = 34598 分钟

<块引用>
= 101 days
  - 28 weekends
  = 73 days (71 minus start and end partial days)
  = 34080 minutes (568 hours x 71 whole days @ 8 hours per day) 
      + start date 07:49 to 17:00 with an hour for lunch (8 hours 11 minutes) = 491 (551 minutes - 60 for lunch)
      + end date 08:00 to 08:27 = 27 minutes
  = 34080 + 491 + 27 = 34598
CREATE TABLE bankHoliday (
    bhDate DATE
) ON [PRIMARY]

-- 2021
INSERT INTO bankHoliday (bhDate) VALUES ('2021-01-01' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-04-02' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-04-05' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-05-03' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-05-31' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-08-30' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-12-27' );
INSERT INTO bankHoliday (bhDate) VALUES ('2021-12-28' );

DROP FUNCTION dbo.[fn_GetTotalWorkingMinutes];
GO

CREATE FUNCTION [fn_GetTotalWorkingMinutes]
(
    @DateFrom Datetime,
    @DateTo Datetime
)
RETURNS INT
AS
BEGIN
    DECLARE @StartOfDay TIME = '08:00:00'
    DECLARE @EndOfDay TIME = '17:00:00'
    DECLARE @Lunch INT = 60
    DECLARE @ThresholdForLunch INT = 300    -- 5 hours x 60 mins, assume x minutes for lunch (working 8:00AM to after 1:00PM, or before 12:00PM to 5:00pm will probably include lunch)
    DECLARE @WorkingMinutes INT = 0

    ;WITH seq(n) AS 
    (
        -- Get an integer sequence number starting from zero, per day between From and To dates
        SELECT 0 UNION ALL SELECT n + 1 FROM seq
        WHERE n < DATEDIFF(DAY, @DateFrom, @DateTo)
    ),
    d(Id,calDate,dayNum) AS
    (
        -- Return the sequence as an Id, the dates between From and To, and the Day-of-week number (1 = Sunday, 7 = Saturday)
        SELECT      n AS Id
        ,           DATEADD(DAY, n, @DateFrom) AS calDate
        ,           DATEPART(dw, DATEADD( DAY, n, @DateFrom)) AS dayNum
        FROM        seq
    ),
    src AS
    (
        SELECT      Id
        ,           CAST(CONVERT(NVARCHAR(10), d.calDate, 126) AS date) AS calDate
        ,           dayNum
        ,           CASE
                        WHEN FORMAT(d.calDate, 'yyyy-MM-dd' ) = FORMAT(@DateFrom, 'yyyy-MM-dd' ) THEN @DateFrom         -- Return the Date From with Start Time
                        ELSE CAST(CONVERT(NVARCHAR(10), d.calDate, 126) AS datetime) + CAST(@StartOfDay AS datetime)    -- Add the Start of Day Time to each day
                    END AS calStartDateTime
        ,           CASE
                        WHEN FORMAT(d.calDate, 'yyyy-MM-dd' ) = FORMAT(@DateTo, 'yyyy-MM-dd' ) THEN @DateTo             -- Return the Date To with End Time
                        ELSE CAST(CONVERT(NVARCHAR(10), d.calDate, 126) AS datetime) + CAST(@EndOfDay AS datetime)      -- Add the End of Day Time to each day
                    END AS calEndDateTime
        FROM        d
        -- Exclude Bank Holidays
        LEFT JOIN   dbo.bankHoliday bh
        ON          bh.bhDate = d.caldate
        WHERE       bh.bhDate IS NULL   -- Exclude Bank Holidays
                    AND d.dayNum > 1    -- After Sunday
                    AND d.dayNum < 7    -- Before Saturday
    )
    /* For Debugging
    SELECT      Id
    ,           calDate
    ,           calStartDateTime
    ,           calEndDateTime
    ,           CASE 
                    WHEN DATEDIFF(minute, calStartDateTime, calEndDateTime) > 5 THEN DATEDIFF(minute, calStartDateTime, calEndDateTime) - @Lunch        -- Take an hour off for lunch if more than 5 hours have been worked 
                    ELSE DATEDIFF(minute, calStartDateTime, calEndDateTime) 
                END AS WorkingHours
    */

    SELECT      @WorkingMinutes = SUM(
                    CASE 
                        WHEN DATEDIFF(minute, calStartDateTime, calEndDateTime) > @ThresholdForLunch THEN DATEDIFF(minute, calStartDateTime, calEndDateTime) - @Lunch   -- Take an hour off for lunch if more than 5 hours have been worked 
                        ELSE DATEDIFF(minute, calStartDateTime, calEndDateTime) 
                    END 
                ) 

    FROM        src
    --ORDER BY  calDate
    OPTION      (MAXRECURSION 0);
  
RETURN (@workingMinutes);

END

GO

SELECT  requested
,       solved
,       dbo.[fn_GetTotalWorkingMinutes](requested, solved)
FROM    MyTable