简体   繁体   English

日期之间的 DATEDIFF 基于营业时间(上午 8 点 - 下午 5 点)而不是总小时数或天数

[英]DATEDIFF between dates based on business hour (8 am - 5 pm) instead of total hours or days

I need to calculate the business hours (8am - 5pm) between different dates.我需要计算不同日期之间的营业时间(上午 8 点至下午 5 点)。 Here is the scenario: Tickets get assigned to employees ( Ticket_Submission (DateTime)), then the employee creates a product ( Build_Date (Datetime)), now sometimes in order to create a product a medication needs to be built ( Med_Date (Datetime)).这是场景:票证被分配给员工( Ticket_Submission (DateTime)),然后员工创建产品( Build_Date (Datetime)),现在有时为了创建产品需要构建药物( Med_Date (Datetime)) . In order to calculate the how long it took for the employee to create a product, I need to DATEDIFF between Ticket_submission AND Build_Date , however, if medication is involved, it had to be between Med_Date AND Build_Date .为了计算员工创建产品所需的时间,我需要在Ticket_submission AND Build_Date之间进行DATEDIFF ,但是,如果涉及药物,它必须在Med_Date AND Build_Date之间。 It is OK to include weekends and holidays.可以包括周末和节假日。 The following helps me to get the difference in days:以下帮助我在几天内获得差异:

.....
,IIF((med_date = buid_date OR ticket_submission = build_date), 1 
, IIF(med_date IS NULL OR med_date < ticket_submission , (DATEDIFF(dd, ticket_submission,build_date))
, DATEDIFF(dd, med_date, build_date))) AS buildcompletedate,….

This only gives me the number of days, also, I know that if instead of DD, I put hours I can get the total number of hours between the 2 dates.这只给了我天数,而且,我知道如果不是 DD,我把小时数放在我可以得到两个日期之间的总小时数。 But, how I can have these in business hours?但是,我怎么能在工作时间拥有这些呢?

I do appreciate your help.我很感激你的帮助。

ticketID  Ticket_submission               Med_Date                Build_Date
1549392    2017-04-07 10:31:06:210        2017-04-08 11:31:06:210  2017-04-09 12:30:08:110
1751406    2017-06-06 4:30:08:200        2018-08-06 3:30:08:200   2018-09-10 3:30:08:200 
2583870    2019-11-20 1:20:01:100        NULL                     2019-11-23 2:20:01:100          

To help construct test sample, here is a common table expression that constructs the test sample in-memory:为了帮助构建测试样本,下面是一个在内存中构建测试样本的通用表表达式:

; with Ticket (TicketID, Ticket_Submission, Med_Date, Build_Date)
AS
(
    SELECT TicketID = 1549392, Ticket_Submission = CAST('2017-04-07 10:31:06:210' AS DATETIME2), Med_Date = CAST('2017-04-08 11:31:06:210' AS DATETIME2), Build_Date = CAST('2017-04-09 12:30:08:110' AS DATETIME2) UNION ALL
    SELECT 1751406, CAST('2017-06-06 4:30:08:200' AS DATETIME2), CAST('2018-08-06 3:30:08:200' AS DATETIME2), CAST('2018-09-10 3:30:08:200 ' AS DATETIME2) UNION ALL
    SELECT 2583870, CAST('2019-11-20 1:20:01:100' AS DATETIME2), CAST(NULL AS DATETIME2), CAST('2019-11-23 2:20:01:100' AS DATETIME2)
)
select

    TicketID,
    0 AS RoundedBusinessHoursBetweenDates /* Update with answer code */
from Ticket

Expected Results预期成绩

TicketID  RoundedBusinessHoursBetweenDates 
--------  --------------------------------
1549392   10
1751406   307
2583870   20????

Rounding四舍五入

  • It's OK to round hours倒计时是可以的
    • for example, instead of 4 hours 30 min, have it as 5 hours.例如,不是 4 小时 30 分钟,而是 5 小时。

Understanding the Calculation, Step-By-Step逐步理解计算

  1. For the first row (TicketID 1549392), I expect to see:对于第一行(TicketID 1549392),我希望看到:
    1. 10 hours ( about 5 hours and 30 min from 11:31 am till 5 pm (for the first day)+ 4.5 hours for 8 am till 12:30 for the second day) 10 小时(第一天上午 11:31 至下午 5 点约 5 小时 30 分钟)+ 第二天上午 8 点至 12:30 约 4.5 小时)
  2. For the second row (TicketID 1751406), I expect to see:对于第二行(TicketID 1751406),我希望看到:
    1. 2 hours for the first day (for the med_build date) + 8 hours for the last day (8 am till 3:30 pm) + 9 * remaining days between these 2 days第一天 2 小时(针对 med_build 日期)+ 最后一天 8 小时(上午 8 点到下午 3:30)+ 9 * 这两天之间的剩余天数
  3. For the third row (TicketID 2583870), if Med_Date is NULL then only the difference from ticket_submission till build_date对于第三行(TicketID 2583870),如果Med_DateNULL ,那么只有从 ticket_submission 到 build_date 的差异

I am not sure your data is valid.我不确定您的数据是否有效。 I assume the date for the 2nd row should be 2018-09-10 15:30:08:200 as 2018-09-10 3:30:08:200 is not within working hours.我假设第二行的日期应该是2018-09-10 15:30:08:200 ,因为2018-09-10 3:30:08:200不在工作时间内。 Anyway, you'll need to calculate the hours in 3 separate parts.无论如何,您需要分 3 个单独的部分计算小时数。 The hours from the start DateTime to 5 pm and the hours from 8 am of the last date to the real last DateTime, plus the working hours in between.从开始 DateTime 到下午 5 点的小时数和从最后一个日期的上午 8 点到真正的最后一个 DateTime 的小时数,加上其间的工作时间。

It's not pretty, but here is the sample code.它不漂亮,但这里是示例代码。 The sum of these 3 columns is your total hours.这 3 列的总和就是您的总小时数。

declare @tblTemp table(ticketId int, ticket_submission datetime, med_date datetime, build_date datetime)
insert into @tblTemp
values(1549392,'2017-04-07 10:31:06:210','2017-04-08 11:31:06:210','2017-04-09 12:30:08:110'),
    (1751406,'2017-06-06 16:30:08:200','2018-08-06 15:30:08:200','2018-09-10 15:30:08:200'),
    (2583870,'2019-11-20 13:20:01:100',null,'2019-11-23 14:20:01:100')

select ticketId, datediff(hour, coalesce(med_date, ticket_submission), convert(varchar(10), coalesce(med_date, ticket_submission), 120) + ' 17:00:00') -- first day hours
    ,(datediff(day, coalesce(med_date, ticket_submission), build_date) - 1) * (17-8) -- hours in between
    ,datediff(hour, convert(varchar(10), build_date, 120) + ' 08:00:00', build_date) -- last day hours
from @tblTemp

To exclude weekends, see this so answer or this one .要排除周末,请参阅this so answerthis one If you want to exclude weekends and holidays, you might need a calendar table.如果您想排除周末和节假日,您可能需要一个日历表。

Try this:尝试这个:

; with Ticket (TicketID, Ticket_Submission, Med_Date, Build_Date)
AS
(
    SELECT TicketID = 1549392, Ticket_Submission = CAST('2017-04-07 10:31:06:210' AS DATETIME2), Med_Date = CAST('2017-04-08 11:31:06:210' AS DATETIME2), Build_Date = CAST('2017-04-09 12:30:08:110' AS DATETIME2) UNION ALL
    SELECT 1751406, CAST('2017-06-06 4:30:08:200' AS DATETIME2), CAST('2018-08-06 3:30:08:200' AS DATETIME2), CAST('2018-09-10 3:30:08:200 ' AS DATETIME2) UNION ALL
    SELECT 2583870, CAST('2019-11-20 1:20:01:100' AS DATETIME2), CAST(NULL AS DATETIME2), CAST('2019-11-23 2:20:01:100' AS DATETIME2)
)
select

    TicketID,
    CASE
        WHEN DATEDIFF(Hour,ISNULL(Med_Date,ticket_submission),build_date)>24
        THEN (DATEDIFF(Hour,ISNULL(Med_Date,ticket_submission),build_date)-15)/24
    END AS DAYS,
    CASE
        WHEN DATEDIFF(Hour,ISNULL(Med_Date,ticket_submission),build_date)>24 AND (DATEDIFF(Hour,ISNULL(Med_Date,ticket_submission),build_date)-15)/24 >0
        THEN DATEDIFF(Hour,ISNULL(Med_Date,ticket_submission),build_date) -(15 * DATEDIFF(DAY,ISNULL(Med_Date,ticket_submission),build_date))-9
        ELSE DATEDIFF(Hour,ISNULL(Med_Date,ticket_submission),build_date) -(15 * DATEDIFF(DAY,ISNULL(Med_Date,ticket_submission),build_date))
    END
from Ticket

The Total_Hours Column will Return the Total number of Columns Between the Two Days and the Days and Hours Column Return the Days and Hours Differences between those Columns. Total_Hours 列将返回两天之间的总列数,而天数和小时数列返回这些列之间的天数和小时数差异。

Hope this Code Works Successfully for your Rows and Sorry for my previous answer i misunderstood your Question.希望此代码对您的行成功运行,对于我之前的回答,我误解了您的问题,我深表歉意。

Anyway Thanks John Zabroski and Martin Smith不管怎样,谢谢约翰·扎布罗斯基和马丁·史密斯

Several aspects were analyzed to implement the solution.分析了几个方面以实施该解决方案。

  1. Schedule (with the days and hours per available for business)时间表(每个可用于业务的日期和时间)
  2. The Holidays and religious (List Day)假期和宗教(名单日)

by means of a function, it establishes the days related to the schedule通过 function,它建立了与时间表相关的天数

and if any of the days worked are holidays, evaluating each of the time ranges如果任何工作日是假期,则评估每个时间范围

already with these relations it is calculated by day how much I worked and calculates the total hours已经有了这些关系,它按天计算了我工作了多少并计算了总小时数

with the code you can understand it better使用代码您可以更好地理解它

CREATE FUNCTION TimeInBusiness
(
    @StarDate as Datetime,
    @EndDate as Datetime,
    @Schedule NVARCHAR(MAX),
    @HolyDay as varchar(3000)
)
RETURNS decimal(18,2)
AS 
BEGIN
    declare @Globalization as varchar(6)='en-US'
    declare @Returnvalue as varchar(4000);
    DECLARE @ListHolyDay TABLE(HolyDay Date);
    DECLARE @ListDays TABLE(Day Date,DayWeek varchar(20),StarTime Time,EndTime Time);

    INSERT INTO @ListHolyDay(HolyDay)
    SELECT try_cast(value as date)
    FROM  STRING_SPLIT(@HolyDay, ',')
    where try_cast(value as date) is not null

    DECLARE @ListBusinessDay TABLE(DayWeek varchar(20),InveralTimeStar Time,InveralTimeEnd Time);
    INSERT INTO @ListBusinessDay(DayWeek,InveralTimeStar,InveralTimeEnd)
    SELECT DayWeek,cast(InveralTimeStar as time) as InveralTimeStar,cast(InveralTimeEnd as time) as InveralTimeEnd
    FROM OPENJSON(@Schedule)
      WITH (
        DayWeek NVARCHAR(20) '$.Interval.DayWeek',
        InveralTimeStar NVARCHAR(20) '$.Interval.InveralTimeStar',
        InveralTimeEnd NVARCHAR(20) '$.Interval.InveralTimeEnd'
      );

    declare @SecondsHour as float=3600
    declare @IntervalStarDate as Date
    declare @IntervalEndDate as Date
    set @IntervalStarDate=cast(@StarDate as date)
    set @IntervalEndDate=cast(@EndDate as date)
    declare @StarTime as varchar(8)=''
    declare @EndTime as varchar(8)=''

    WHILE (@IntervalStarDate<=@IntervalEndDate) 
    BEGIN
        if (@IntervalStarDate<@StarDate)
        begin
            set @StarTime=FORMAT( @StarDate, 'HH:mm:ss', @Globalization )
        end
        else
        begin
            set @StarTime=FORMAT( @IntervalStarDate, 'HH:mm:ss', @Globalization )
        end
        if (@IntervalStarDate<@IntervalEndDate)
        begin
            set @EndTime='23:59:59'
        end
        else
        begin
            set @EndTime=FORMAT( @EndDate, 'HH:mm:ss', @Globalization )
        end
        INSERT INTO @ListDays(Day,DayWeek,StarTime,EndTime )VALUES (@IntervalStarDate, FORMAT( @IntervalStarDate, 'ddd', @Globalization ),cast(@StarTime as time),cast(@EndTime as time)) 
    set @IntervalStarDate=DATEADD(Day,1,@IntervalStarDate)
    END;

    with ForDayTime as 
    (
    select ListDays.Day,ListBusinessDay.DayWeek ,ListBusinessDay.InveralTimeStar,ListBusinessDay.InveralTimeEnd,
    ListDays.StarTime,ListDays.EndTime,
    CASE
        WHEN ListBusinessDay.InveralTimeStar<=ListDays.StarTime THEN ListDays.StarTime
        ELSE ListBusinessDay.InveralTimeStar
    END as StarTimeDay
    ,
    CASE
        WHEN ListBusinessDay.InveralTimeEnd>=ListDays.EndTime THEN ListDays.EndTime
        ELSE ListBusinessDay.InveralTimeEnd
    END as EndTimeDay
    from @ListDays as ListDays  
     inner join @ListBusinessDay as ListBusinessDay on ListDays.DayWeek =ListBusinessDay.DayWeek 
    where (ListDays.Day not in (select HolyDay from @ListHolyDay))
    )
    select 
@Returnvalue=isnull(sum(datediff(SECOND,  StarTimeDay,EndTimeDay)) /@SecondsHour,0) 
    from ForDayTime
    where (StarTimeDay<EndTimeDay)
    RETURN @Returnvalue;
    END;

Example Use示例使用

declare @StarDate as Datetime='2020-04-10 10:00:00'
declare @EndDate as Datetime='2020-04-20 12:59:00'
DECLARE @Schedule NVARCHAR(MAX);
SET @Schedule = N'[
{"Interval": {"DayWeek": "Mon", "InveralTimeStar": "09:30", "InveralTimeEnd": "11:30"}},
{"Interval": {"DayWeek": "Mon", "InveralTimeStar": "13:30", "InveralTimeEnd": "16:30"}},
{"Interval": {"DayWeek": "Tue", "InveralTimeStar": "08:30", "InveralTimeEnd": "16:30"}},
{"Interval": {"DayWeek": "Wed", "InveralTimeStar": "08:30", "InveralTimeEnd": "16:30"}},
{"Interval": {"DayWeek": "Thu", "InveralTimeStar": "08:30", "InveralTimeEnd": "16:30"}},
{"Interval": {"DayWeek": "Fri", "InveralTimeStar": "08:30", "InveralTimeEnd": "12:30"}}
]';
declare @HolyDay as varchar(3000)='2020-04-16,2020-04-17'
select 
dbo.TimeInBusiness('2020-04-10 10:00:00','2020-04-20 12:59:00',@Schedule,@HolyDay) as result,
dbo.TimeInBusiness('2020-04-10 10:00:00','2020-04-20 12:59:00',@Schedule,'') as result2,
dbo.TimeInBusiness('2020-04-01 10:00:00','2020-04-20 12:59:00',@Schedule,@HolyDay) as result
Go

example used funtion示例使用函数

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM