【问题标题】:DATEDIFF between dates based on business hour (8 am - 5 pm) instead of total hours or daysDATEDIFF 基于营业时间(上午 8 点 - 下午 5 点)而不是总小时数或天数
【发布时间】:2020-04-12 16:16:50
【问题描述】:

我需要计算不同日期之间的营业时间(上午 8 点至下午 5 点)。这是场景: 票被分配给员工 (Ticket_Submission (DateTime)),然后员工创建产品 (Build_Date (Datetime)),现在有时为了创建产品需要制造药物 (Med_Date (Datetime) )。为了计算员工创建产品所需的时间, 我需要DATEDIFFTicket_submission AND Build_Date 之间,但是,如果涉及药物,它必须在Med_Date AND Build_Date 之间。可以包括周末和节假日。以下帮助我了解天数的不同:

.....
,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,….

这只给了我天数,而且,我知道如果不是 DD,我输入小时数,我可以获得两个日期之间的总小时数。但是,我怎样才能在工作时间拥有这些?

非常感谢您的帮助。

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          

为了帮助构建测试样本,这里有一个在内存中构建测试样本的通用表表达式:

; 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

预期结果

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

四舍五入

  • 可以整小时
    • 例如,不是 4 小时 30 分钟,而是 5 小时。

逐步理解计算

  1. 对于第一行 (TicketID 1549392),我希望看到:
    1. 10 小时(第一天上午 11:31 至下午 5 点约 5 小时 30 分钟)+ 第二天上午 8 点至 12:30 约 4.5 小时)
  2. 对于第二行 (TicketID 1751406),我希望看到:
    1. 第一天 2 小时(针对 med_build 日期)+ 最后一天 8 小时(上午 8 点到下午 3:30)+ 9 * 这两天之间的剩余天数
  3. 对于第三行(TicketID 2583870),如果Med_DateNULL,那么只有从ticket_submission 到build_date 的区别

【问题讨论】:

  • 如果您可以将测试数据以文本格式并提供营业时间会更容易
  • 文本形式的示例数据和所需结果将是最有帮助的。
  • @Avi 已发布,谢谢。
  • @JohnCappelletti,已发布,谢谢。
  • 必须考虑假期(每个国家/地区可能有不同的日子).. 可以实现一个标量函数,返回已过去的工作小时数,工作日间隔及其时间表,休息日和节假日

标签: sql sql-server


【解决方案1】:

我不确定您的数据是否有效。我假设第二行的日期应该是2018-09-10 15:30:08:200,因为2018-09-10 3:30:08:200 不在工作时间内。无论如何,您需要分 3 个单独的部分计算小时数。从开始 DateTime 到下午 5 点的小时数和从最后一个日期的上午 8 点到真正的最后一个 DateTime 的小时数,加上其间的工作时间。

它并不漂亮,但这里是示例代码。这 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

要排除周末,请参阅so answerthis one。如果您想排除周末和节假日,您可能需要一个日历表。

【讨论】:

  • 非常感谢您的宝贵时间!你能帮我理解剧本吗?对于以下行: select datediff(hour, coalesce(med_date, ticket_submission), convert(varchar(19), convert(date, coalesce(med_date, ticket_submission))) + '17:00:00') -- 第一天的小时数,(datediff(day, coalesce(med_date, ticket_submission), build_date) - 1) * (17-8) -- 间隔时间 ,datediff(hour, convert(varchar(19), convert(date, build_date)) + ' 08:00:00', build_date) -- 最后一天的时间@Weihui Guo
  • 谢谢,你能解释一下第一行和第三行吗?我的意思是:选择 datediff(hour, coalesce(med_date, ticket_submission), convert(varchar(19), convert(date, coalesce(med_date, ticket_submission))) + ' 17:00:00') 和 datediff(hour, convert( varchar(19), convert(date, build_date)) + '08:00:00', build_date) @Weihui Guo
  • 很抱歉不清楚,我想了解什么 convert(varchar(19), convert(date, coalesce(med_date, ticket_submission))) + '17:00:00')我是否知道合并,给了我第一个不为空的项目,但是为什么 convert(varchar(19)... 紧随其后,然后又是 date...
  • 谢谢!只是我的最后一个问题,拜托。为什么我需要添加“17:00:00”或添加“08:00:00”,因为我实际上是在计算直到下午 5 点的时间,我不应该减去它吗?这是让我感到困惑的部分......添加这两个数字。再次感谢,你救了我的命!
  • 完全有道理!我非常感谢您的时间和专业知识!
【解决方案2】:

试试这个:

; 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

Total_Hours 列将返回两天之间的总列数,而Days and Hours 列将返回这些列之间的天数和小时差。

希望此代码对您的行成功运行,抱歉我之前的回答误解了您的问题。

无论如何感谢 John Zabroski 和 Martin Smith

【讨论】:

  • 这在客观上是不正确的,因为@nina_dev 指出第一行应该是 10 小时的差异。您没有考虑周末和节假日。
  • @JohnZabroski 提问者并不关心那些“可以包括周末和节假日。”
  • 那样的话,答案还是错了,不过我猜错了原因。请查看我对 nina_dev 问题的更新格式。如果你插入Thiyagu的计算,客观上它并没有给出预期的结果。
  • @JohnZabroski 我已经更正了代码。你可以检查并更新我是否正确
  • @Thiyagu 我认为您的新答案非常接近正确。我认为 nina_dev 需要详细说明舍入的工作原理,然后它可能是正确的。我相信您需要在开始日期取最接近小时的“FLOOR”,在结束日期取最接近小时的“CEILING”。
【解决方案3】:

分析了几个方面来实施该解决方案。

  1. 日程安排(包括可用于营业的日期和时间)
  2. 假期和宗教(名单日)

通过一个函数,它建立与日程相关的天数

如果任何工作日是假期,则评估每个时间范围

已经有了这些关系,它是按天计算我工作了多少并计算总小时数

通过代码你可以更好地理解它

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;

示例使用

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

【讨论】:

    猜你喜欢
    • 2019-04-12
    • 1970-01-01
    • 2022-01-16
    • 2022-01-07
    • 2014-09-08
    • 1970-01-01
    • 2014-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多