2011-03-11 153 views
26

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

+3

我假设你并不需要采取节假日等考虑在内? – Blorgbeard 2011-03-11 14:37:06

+2

...什么午餐休息时间,茶歇等? – 2011-03-11 14:48:06

+4

是的,我不采取国家假期和休息。 – Baran 2011-03-11 18:46:54

回答

23

巴兰的答案固定,并修改所有的变量(周末,节假日等) 2005年SQL

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 @[email protected] 
    END 
    IF (@FinishTime<@WorkStart) 
    BEGIN 
     SET @[email protected] 
    END 
    IF (@StartTime>@WorkFinish) 
    BEGIN 
     SET @StartTime = @WorkFinish 
    END 

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

    WHILE(@CurrentDate<[email protected]) 
    BEGIN  
     IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7) 
     BEGIN 
      IF (@[email protected]) AND (@[email protected]) 
      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 (@[email protected]) AND (@[email protected]) 
      BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish) 
      END 

      ELSE IF (@[email protected]) AND (@[email protected]) 
      BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime) 
      END 
      --IF it starts and finishes in the same date 
      ELSE IF (@[email protected]) AND (@[email protected]) 
      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 @[email protected] 
    END 
    IF (@FinishTime<@WorkStart) 
    BEGIN 
     SET @F[email protected] 
    END 
    IF (@StartTime>@WorkFinish) 
    BEGIN 
     SET @StartTime = @WorkFinish 
    END 

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

    WHILE(@CurrentDate<[email protected]) 
    BEGIN  
     IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7) 
     BEGIN 
      IF (@[email protected]) AND (@[email protected]) 
      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 (@[email protected]) AND (@[email protected]) 
      BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish) 
      END 

      ELSE IF (@[email protected]) AND (@[email protected]) 
      BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime) 
      END 
      --IF it starts and finishes in the same date 
      ELSE IF (@[email protected]) AND (@[email protected]) 
      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 
+0

帮助很多!谢谢 – 2013-09-24 08:04:59

+0

我在这里有一个问题,@Temp返回仅差几分钟? – Somebody 2014-02-03 16:38:12

+0

如果你知道1小时= 60分钟等,你可以很容易地转换它: ) – 2015-06-17 09:15:44

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 
+0

谢谢,但我在开始和结束日期有几个小时。我们如何在这个计算中增加这个未来? – Baran 2011-03-11 18:48:57

+0

@Baran:看看我的解决方案 – 2016-11-15 09:31:50

6

从@ 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 

...这可以算出来作为一个更简单的方法,如果你需要开始添加更复杂的规则什么定义了一个工作日,或者如果你的工作时间取决于一天等等。

它也使规则更容易“可编辑”,因为你不需要改变任何实际的代码来改变定义一个工作日,增加公众假期等。

0

我h AVE实际上这样做之前,考虑到工作时间是非常困难的,我觉得这个任务最好SQL之外完成

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 @[email protected] 
    END 

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

    WHILE(@CurrentDate<[email protected]) 
    BEGIN  
     IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7) 
     BEGIN 
      IF (@[email protected]) AND (@[email protected]) 
      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 (@[email protected]) AND (@[email protected]) 
      BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish) 
      END 

      ELSE IF (@[email protected]) AND (@[email protected]) 
      BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime) 
      END 
      --IF it starts and finishes in the same date 
      ELSE IF (@[email protected]) AND (@[email protected]) 
      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 
+0

在以下日期无法正常工作:SELECT dbo。[WorkTime]('2012-04-18 9:30','2012-04-18 17:31') - 返回481而不是480 – Kodak 2012-04-18 07:02:15

+0

“如果它启动并在同一日期结束”条件在未对齐的日期而不是对齐的时间上工作;我将发布固定版本作为新答案,或者我会尝试编辑上面的 – Kodak 2012-04-18 07:10:26

+0

以上的WorkStart,WorkFinish和每天的工作时间不同步 - 最好动态计算后者 - 将做另一个编辑 – Kodak 2012-04-18 07:22:14

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 @[email protected] 
    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<[email protected]) 
BEGIN  

     IF (DATEPART(dw, @CurrentDate)!=1 AND DATEPART(dw, @CurrentDate)!=7) 
     BEGIN 
       IF (@[email protected]) AND (@[email protected]) 
       BEGIN 
        SET @Temp = (@Temp + (8*60)) 

       END 

       ELSE IF (@[email protected]) AND (@[email protected]) 
       BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @StartTime, @WorkFinish) 

       END 

       ELSE IF (@[email protected]) AND (@[email protected]) 
       BEGIN 
       SET @Temp = @Temp + DATEDIFF(MINUTE, @WorkStart, @FinishTime) 

       END 

       ELSE IF (@[email protected]) AND (@[email protected]) 
       BEGIN 
       SET @Temp = DATEDIFF(MINUTE, @StartTime, @FinishTime) 

       END 

      END 

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

END 
     Return @TEMP 

END 
18

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

函数返回以分钟为单位的时间间隔 - 您可以除以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 [email protected] 
     Set @[email protected] 
     Set @[email protected] 
     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 [email protected] 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)>[email protected]+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)>[email protected]+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 @[email protected] + 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 @[email protected] + (@[email protected])*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 @[email protected] + DATEDIFF(MI, @StartDate, @EndDate) Else Set @[email protected] + DATEDIFF(MI, DateAdd(hour, @StartHour, DateDiff(DAY, 0, @StartDate)), @EndDate) 
    End 
    If @Reverse=1 Set @[email protected] 
    Return @WorkMin 
End 
+0

这很好用!谢谢! – buzzzzjay 2013-01-31 22:05:21

+1

太棒了,谢谢 – Craig 2014-11-26 15:41:42

+0

这个代码恐怕有一个小错误。如果您的StartDate属于周末或假期,则会在开始日期的1天内加入帐户。因此,如果您的开始时间是星期日下午1点,它将从星期一下午1点开始计算,而不是星期一的StartHour。若要更正此问题,请更改两个相同的行,这些行读取:“Else Set(at)StartDate = DATEADD(DD,1,(at)StartDate)”to this:“Else Set(at)StartDate = DATEADD(HOUR,(at)开始时间,CAST(CAST(DATEADD(DD,1,(at)StartDate)AS DATE)AS DATETIME))“ – 2017-10-14 00:09:59

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 [email protected] - @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 @[email protected][email protected] 
     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 @[email protected] 
     if @Date1<>@Date2 
      set @[email protected][email protected] 
     Else 
      Set @[email protected][email protected]@WorkDayLong 

    End 
    return @minutes 
End 
1

这里是一个内联版本 开始/结束日期像2015-03-16 09:52:24。000 开始/结束时间(businesshours)喜欢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 
0

你怎么看待这个解决方案是什么?

不使用循环 “虽然”。

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 
0

带while循环的2008版本对于我们的工作流程来说太慢了。如果我们在StartDate和FinishDate之间运行几百万次的计算一百万行,上面的循环版本需要10分钟的时间在我们的SQL服务器上。我的版本在1分钟内完成相同的任务。

下面是没有环(2008年测试),供您参考函数:

ALTER FUNCTION dbo.WorkTime (
@EventStartDateTime DATETIME, 
@EventEndDateTime DATETIME, 
@BusinessStartDateTime DATETIME, 
@BusinessEndDateTime DATETIME, 
@IncludeWeekend BIT) 
RETURNS BIGINT 
AS 
BEGIN 

    -- Purpose: Function that calculates seconds of business hours between an event. 
    -- Allows the calculation to work with (@IncludeWeekend = 1) or without weekends (@IncludeWeekend = 0), that is working days only or seven days a week. 
    -- Allows the calculation to work for 24x7 business hours 

    -- Author Mathias Florin 
    -- Create Date: 2016/11/05 

    -- Comments: Event StartDateTime (ESDT), Event EndDateTime(EETD) 
    --    Business StartDateTime (BSDT), Business EndDateTime (BEDT) 
    --    Event StartHour (ESH), Event EndHour(EEH) 
    --    Business StartHour (BSH), Business EndHour (BEH) 

    --    For 24x7 business hours pass these parameters to the function: 
    --    BusinessStartDateTime (1900-01-01 00:00:00) and BusinessEndDateTime (1900-01-02 00:00:00) 

    -- Check Statement: SET DATEFIRST 7; SELECT dbo.WorkTime('2016-11-05 08:00:00','2016-11-05 18:00:00','1900-01-01 09:00:00','1900-01-01 17:00:00',1) 

    -- Different conditions of calculation which are referenced in the SQL code below: 

    -- Same day incl. weekend - Business not working 24x7: 
    --            StartDateTime & EndDateTime on same day 
    -- 1) ESH BSH <-- 0 day --> BEH EEH  -> DIFF(BSH,BEH)  
    -- 2) BSH ESH <-- 0 day --> BEH EEH  -> DIFF(ESH,BEH)  
    -- 3) ESH BSH <-- 0 day --> EEH BEH  -> DIFF(BSH,EEH)  
    -- 4) BSH ESH <-- 0 day --> EEH BEH  -> DIFF(ESH,EEH)  

    -- Different day incl. weekend - Business not working 24x7:      
    --            ESDT   Days in between      EETD 
    -- 5) ESH BSH <-- 1..n day --> BEH EEH -> DIFF(BSH, BEH) (Daydiff(all days) -1) * (BSH,BEH) DIFF(BSH,BEH) 
    -- 6) BSH ESH <-- 1..n day --> BEH EEH -> DIFF(ESH,BEH) (Daydiff(all days) -1) * (BSH,BEH) DIFF(BSH,BEH) 
    -- 7) ESH BSH <-- 1..n day --> EEH BEH -> DIFF(BSH, BEH) (Daydiff(all days) -1) * (BSH,BEH) DIFF(BSH,EEH) 
    -- 8) BSH ESH <-- 1..n day --> EEH BEH -> DIFF(ESH,BEH) (Daydiff(all days) -1) * (BSH,BEH) DIFF(BSH,EEH) 

    -- Same day excl. weekend - Business not working 24x7:      
    --            ESDT & EETD on same day 
    -- 9) ESH BSH <-- 0 day --> BEH EEH  -> Workday THEN DIFF(BSH,BEH) ELSE 0 END  
    -- 10) BSH ESH <-- 0 day --> BEH EEH  -> Workday THEN DIFF(ESH,BEH) ELSE 0 END  
    -- 11) ESH BSH <-- 0 day --> EEH BEH  -> Workday THEN DIFF(BSH,EEH) ELSE 0 END  
    -- 12) BSH ESH <-- 0 day --> EEH BEH  -> Workday THEN DIFF(ESH,EEH) ELSE 0 END  
    --       
    -- Different day excl. weekend - Business not working 24x7:      
    --            ESDT         | Workdays in between      | EETD 
    -- 13) ESH BSH <-- 1..n day --> BEH EEH -> (Workday THEN DIFF(BSH, BEH) ELSE 0) + ((Daydiff(workdays) -1) * (BSH,BEH)) + (Workday THEN DIFF(BSH,BEH) ELSE 0) 
    -- 14) BSH ESH <-- 1..n day --> BEH EEH -> (Workday THEN DIFF(ESH,BEH) ELSE 0)  + ((Daydiff(workdays) -1) * (BSH,BEH)) + (Workday THEN DIFF(BSH,BEH) ELSE 0) 
    -- 15) ESH BSH <-- 1..n day --> EEH BEH -> (Workday THEN DIFF(BSH, BEH) ELSE 0) + ((Daydiff(workdays) -1) * (BSH,BEH)) + (Workday THEN DIFF(BSH,EEH) ELSE 0) 
    -- 16) BSH ESH <-- 1..n day --> EEH BEH -> (Workday THEN DIFF(ESH,BEH) ELSE 0)  + ((Daydiff(workdays) -1) * (BSH,BEH)) + (Workday THEN DIFF(BSH,EEH) ELSE 0) 

    -- Business working 24x7 
    -- 17) Date difference in seconds between EventStartDateTime and EventEndDateTime 

    DECLARE @EventStartDateTimeHour TIME; SET @EventStartDateTimeHour = CAST(@EventStartDateTime AS TIME); 
    DECLARE @EventEndDateTimeHour TIME; SET @EventEndDateTimeHour = CAST(@EventEndDateTime AS TIME); 
    DECLARE @BusinessStartDateTimeHour TIME; SET @BusinessStartDateTimeHour = CAST(@BusinessStartDateTime AS TIME); 
    DECLARE @BusinessEndDateTimeHour TIME; SET @BusinessEndDateTimeHour = CAST(@BusinessEndDateTime AS TIME); 
    DECLARE @EventStartDateTimeIsWorkingDay BIT = CASE 
     WHEN DATENAME(DW, @EventStartDateTime) IN ('Saturday', 'Sunday') THEN 0 
     ELSE 1 
    END 
    DECLARE @EventEndDateTimeIsWorkingDay BIT = CASE 
     WHEN DATENAME(DW, @EventEndDateTime) IN ('Saturday', 'Sunday') THEN 0 
     ELSE 1 
    END 
    DECLARE @NextDayEventStartDateTime DATETIME = DATEADD(DAY, 1, @EventStartDateTime); 
    DECLARE @PreviousDayEventStartDateTime DATETIME = DATEADD(DAY, -1, @EventEndDateTime); 
    DECLARE @WorkDaysBetweenEventStartDateTimeAndEventEndDateTime INT = (SELECT 
      (DATEDIFF(dd, @NextDayEventStartDateTime, @PreviousDayEventStartDateTime) + 1) 
      - ((DATEDIFF(wk, @NextDayEventStartDateTime, @PreviousDayEventStartDateTime)) * 2) 
      - (CASE 
       WHEN DATENAME(dw, @NextDayEventStartDateTime) = 'Sunday' THEN 1 
       ELSE 0 
      END) 
      - (CASE 
       WHEN DATENAME(dw, @PreviousDayEventStartDateTime) = 'Saturday' THEN 1 
       ELSE 0 
      END)) 


    DECLARE @Temp BIGINT 
    SELECT @Temp = MAX(Second) 
    FROM (SELECT 
      'Start' AS Grouping 
     , 0 AS Second 
     UNION 
     SELECT 
      'Condition 1' AS Grouping 
     , DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 2' AS Grouping 
     , DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 3' AS Grouping 
     , DATEDIFF(SECOND, @BusinessStartDateTimeHour, @EventEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 4' AS Grouping 
     , DATEDIFF(SECOND, @EventStartDateTimeHour, @EventEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 5' AS Grouping 
     , DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) + ((DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) - 1) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 6' AS Grouping 
     , DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessEndDateTimeHour) + ((DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) - 1) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 7' AS Grouping 
     , DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) + ((DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) - 1) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + DATEDIFF(SECOND, @BusinessStartDateTimeHour, @EventEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 8' AS Grouping 
     , DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessEndDateTimeHour) + ((DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) - 1) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + DATEDIFF(SECOND, @BusinessStartDateTimeHour, @EventEndDateTimeHour) 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 1 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 9' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 10' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 11' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @EventEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 12' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @EventStartDateTimeHour, @EventEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) = 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 13' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END + ((@WorkDaysBetweenEventStartDateTimeAndEventEndDateTime) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + CASE 
       WHEN @EventEndDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 14' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END + ((@WorkDaysBetweenEventStartDateTimeAndEventEndDateTime) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + CASE 
       WHEN @EventEndDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) >= 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 15' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END + ((@WorkDaysBetweenEventStartDateTimeAndEventEndDateTime) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + CASE 
       WHEN @EventEndDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @EventEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) > 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 16' AS Grouping 
     , CASE 
       WHEN @EventStartDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessEndDateTimeHour) 
       ELSE 0 
      END + ((@WorkDaysBetweenEventStartDateTimeAndEventEndDateTime) * DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour)) + CASE 
       WHEN @EventEndDateTimeIsWorkingDay = 1 THEN DATEDIFF(SECOND, @BusinessStartDateTimeHour, @EventEndDateTimeHour) 
       ELSE 0 
      END 
     WHERE DATEDIFF(DAY, @EventStartDateTime, @EventEndDateTime) > 0 
     AND @IncludeWeekend = 0 
     AND DATEDIFF(SECOND, @EventStartDateTimeHour, @BusinessStartDateTimeHour) <= 0 
     AND DATEDIFF(SECOND, @BusinessEndDateTimeHour, @EventEndDateTimeHour) < 0 
     AND DATEDIFF(SECOND, @BusinessStartDateTimeHour, @BusinessEndDateTimeHour) > 0 
     AND DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) = 0 
     UNION 
     SELECT 
      'Condition 17' AS Grouping 
     , DATEDIFF(SECOND, @EventStartDateTime, @EventEndDateTime) 
     WHERE DATEDIFF(DAY, @BusinessStartDateTime, @BusinessEndDateTime) > 0) AS SecondsPassed 
    RETURN @Temp 

END 
+0

mathias florin的解决方案干净简单,但存在条件16的错误。如果使用营业时间8 5,活动开始时间是下午5点以后,第二天上午9点结束。条件16的另一个失败是如果在下午5点后开始并在第二天上午8点之前完成。 – Joel 2016-11-11 15:37:39

+0

@Joel:关于第一次失败:如果事件在17:15:00开始,第二天在09:00:00结束,营业时间为08:00:00至17:00:00,则函数返回3600秒这是一个小时。这是正确的,因为企业有机会在早上(08:00-09:00)在活动中工作一小时(3600秒)。 关于第二次失败,如果事件在营业时间之后开始并在早上营业时间之前结束,则条件16返回-3600。结果与0(cond。starting)结合,返回最大值。 MAX(-3600,0)返回0,这是正确的 - 没有时间花在事件上。 – 2016-11-14 14:22:38

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返回指定开始日期结束日期之间交叉指定的日期部分边界的计数。

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

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; 
7

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

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(的总小时数的输出乘以上述第一步骤i呐天),然后后来并称到第二步骤的输出:

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 

完整的方法,在这篇文章中descibed: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

相关问题