计算两个日期之间的分钟时间差(仅限工作时间)

时间:2010-07-21 06:58:48

标签: sql sql-server sql-server-2005 sql-server-2008

我需要计算数据库中事件的“活动分钟数”。开始时间众所周知。

复杂的是,这些活跃的会议记录只应在一个工作日内计算 - 周一至周五上午9点至下午6点30分,不包括周末和(已知)假日天数列表

开始或“当前”时间可能在工作时间之外,但仍然只计算工作时间。

这是SQL Server 2005,因此可以使用T-SQL或托管程序集。

5 个答案:

答案 0 :(得分:5)

如果你想这样做纯SQL,这是一种方法

CREATE TABLE working_hours (start DATETIME, end DATETIME);

现在用可数周期填充工作时间表,每年约250行。

如果您有一个事件(@event_start,@ event_end)将从小时开始并以小时结束,那么简单查询

SELECT SUM(end-start) as duration
FROM working_hours
WHERE start >= @event_start AND end <= @event_end

就足够了。

另一方面,如果事件在工作时间内开始和/或结束,则查询更复杂

SELECT SUM(duration) 
FROM 
(
   SELECT SUM(end-start) as duration
   FROM working_hours
   WHERE start >= @event_start AND end <= @event_end
UNION ALL
   SELECT end-@event_start
   FROM working_hours
   WHERE @event_start between start AND end
UNION ALL
   SELECT @event_end - start
   FROM working_hours
   WHERE @event_end between start AND end
) AS u

注意:

  • 以上是未经测试的查询,根据您的RDBMS,您可能需要日期/时间函数来聚合和减去日期时间(并且根据上述查询使用的函数可以使用任何时间精度)。
  • 可以重写查询以不使用UNION ALL。
  • working_hours表可用于系统中的其他内容并允许最大的灵活性

编辑: 在MSSQL中,您可以使用DATEDIFF(mi,start,end)来获取上面每个减法的分钟数。

答案 1 :(得分:2)

使用非理性的出色起点,这是SQL Server 2012的TSQL实现。

第一个SQL使用我们的工作日和时间填充表格,不包括周末和假日:

declare @dteStart date
declare @dteEnd date
declare @dtStart smalldatetime
declare @dtEnd smalldatetime
Select @dteStart = '2016-01-01'
Select @dteEnd = '2016-12-31'

CREATE TABLE working_hours (starttime SMALLDATETIME, endtime SMALLDATETIME);

while @dteStart <= @dteEnd
BEGIN
   IF    datename(WEEKDAY, @dteStart) <> 'Saturday' 
     AND DATENAME(WEEKDAY, @dteStart) <> 'Sunday'
     AND @dteStart not in ('2016-01-01' --New Years
                          ,'2016-01-18' --MLK Jr
                          ,'2016-02-15' --President's Day
                          ,'2016-05-30' --Memorial Day
                          ,'2016-07-04' --Fourth of July
                          ,'2016-09-05' --Labor Day
                          ,'2016-11-11' --Veteran's Day
                          ,'2016-11-24' --Thanksgiving
                          ,'2016-11-25' --Day after Thanksgiving
                          ,'2016-12-26' --Christmas
                          )
      BEGIN
        select @dtStart = SMALLDATETIMEFROMPARTS(year(@dteStart),month(@dteStart),day(@dteStart),8,0) --8:00am
        select @dtEnd   = SMALLDATETIMEFROMPARTS(year(@dteStart),month(@dteStart),day(@dteStart),17,0) --5:00pm
        insert into working_hours values (@dtStart,@dtEnd)
      END
   Select @dteStart = DATEADD(day,1,@dteStart)
END

现在这里的逻辑用于将分钟作为INT返回:

declare @event_start datetime2
declare @event_end datetime2
select @event_start = '2016-01-04 8:00'
select @event_end = '2016-01-06 16:59'

SELECT SUM(duration) as minutes
FROM 
(
   SELECT DATEDIFF(mi,@event_start,@event_end) as duration
   FROM working_hours
   WHERE @event_start >= starttime 
     AND @event_start <= endtime
     AND @event_end <= endtime
UNION ALL
   SELECT DATEDIFF(mi,@event_start,endtime)
   FROM working_hours
   WHERE @event_start >= starttime 
     AND @event_start <= endtime
     AND @event_end > endtime
UNION ALL
   SELECT DATEDIFF(mi,starttime,@event_end)
   FROM working_hours
   WHERE @event_end >= starttime 
     AND @event_end <= endtime
     AND @event_start < starttime
UNION ALL
   SELECT SUM(DATEDIFF(mi,starttime,endtime))
   FROM working_hours
   WHERE starttime > @event_start
     AND endtime < @event_end
) AS u

这正确地返回了3个9小时工作日的1分钟

答案 2 :(得分:1)

我来到这里寻找一个非常相似的问题的答案 - 我需要获得2个日期之间的分钟,不包括周末,不包括08:30和18:00之外的小时。经过一番黑客攻击后,我想我已经整理好了。以下是我的表现。欢迎您的想法 - 谁知道,也许有一天我会报名参加这个网站:)

create function WorkingMinutesBetweenDates(@dteStart datetime, @dteEnd datetime)
returns int
as
begin

declare @minutes int
set @minutes = 0

while @dteEnd>=@dteStart
    begin

        if  datename(weekday,@dteStart) <>'Saturday' and  datename(weekday,@dteStart)<>'Sunday'
            and (datepart(hour,@dteStart) >=8 and datepart(minute,@dteStart)>=30 )
            and (datepart(hour,@dteStart) <=17)

            begin
                set @minutes = @minutes + 1
            end     

        set @dteStart = dateadd(minute,1,@dteStart)
    end

return @minutes
end
go

答案 3 :(得分:1)

我开始使用Unreason发布的内容,这是一个很好的开始。我测试了这是SQL Server,发现并非所有时间都被捕获。我认为问题主要发生在事件开始和结束的同一天。这个解决方案对我来说似乎运作良好

CREATE TABLE [dbo].[working_hours](
[wh_id] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
[wh_starttime] [datetime] NULL,
[wh_endtime] [datetime] NULL,
PRIMARY KEY CLUSTERED 
(
[wh_id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF,           ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

CREATE FUNCTION [dbo].[udFWorkingMinutes] 
(
@startdate DATETIME
,@enddate DATETIME
)
RETURNS INT
AS
BEGIN


DECLARE @WorkingHours INT
SET @WorkingHours = 
(SELECT 
CASE WHEN COALESCE(SUM(duration),0) < 0 THEN 0 ELSE SUM(Duration) 
END AS Minutes
FROM 
(
    --All whole days
   SELECT ISNULL(DATEDIFF(mi,wh_starttime,wh_endtime),0) AS Duration
   FROM working_hours
   WHERE wh_starttime >= @startdate AND wh_endtime <= @enddate 
   UNION ALL
   --All partial days where event start after office hours and finish after office hours
   SELECT ISNULL(DATEDIFF(mi,@startdate,wh_endtime),0) AS Duration
   FROM working_hours
   WHERE @startdate > wh_starttime AND @enddate >= wh_endtime 
   AND (CAST(wh_starttime AS DATE) = CAST(@startdate AS DATE))
   AND @startdate < wh_endtime
   UNION ALL
   --All partial days where event starts before office hours and ends before day end
   SELECT ISNULL(DATEDIFF(mi,wh_starttime,@enddate),0) AS Duration
   FROM working_hours
   WHERE @enddate < wh_endtime 
   AND @enddate >= wh_starttime
   AND @startdate <= wh_starttime 
   AND (CAST(wh_endtime AS DATE) = CAST(@enddate AS DATE))
   UNION ALL  
    --Get partial day where intraday event
   SELECT ISNULL(DATEDIFF(mi,@startdate,@enddate),0) AS Duration
   FROM working_hours
   WHERE @startdate > wh_starttime AND @enddate < wh_endtime 
   AND (CAST(@startdate AS DATE)= CAST(wh_starttime AS DATE))
   AND (CAST(@enddate AS DATE)= CAST(wh_endtime AS DATE))
 ) AS u)

 RETURN @WorkingHours
END
GO

剩下要做的就是用

之类的东西填充工作时间表
;WITH cte AS (
SELECT CASE WHEN DATEPART(Day,'2014-01-01 9:00:00 AM') = 1 THEN '2014-01-01 9:00:00 AM' 
ELSE DATEADD(Day,DATEDIFF(Day,0,'2014-01-01 9:00:00 AM')+1,0) END AS      myStartDate,
CASE WHEN DATEPART(Day,'2014-01-01 5:00:00 PM') = 1 THEN '2014-01-01 5:00:00 PM' 
ELSE DATEADD(Day,DATEDIFF(Day,0,'2014-01-01 5:00:00 PM')+1,0) END AS myEndDate
UNION ALL
SELECT DATEADD(Day,1,myStartDate), DATEADD(Day,1,myEndDate)
FROM cte
WHERE DATEADD(Day,1,myStartDate) <=  '2015-01-01'
)
INSERT INTO working_hours
SELECT myStartDate, myEndDate
FROM cte
OPTION (MAXRECURSION 0)

delete from working_hours where datename(dw,wh_starttime) IN ('Saturday', 'Sunday')

--delete public holidays

delete from working_hours where CAST(wh_starttime AS DATE) = '2014-01-01'

我的第一篇文章!是仁慈的。

答案 4 :(得分:0)

在全球范围内,您需要:

  1. 捕获事件结束时间的方法(可能通过通知,或者首先启动事件的任何内容),以及记录此开始和结束时间的表。
  2. 包含要计算的所有期间(开始和结束)的帮助程序表。 (然后你需要一些支持代码来保持这个表在未来更新)
  3. 存储过程将:
    • 遍历此帮助程序表并找到“活动”句点
    • 计算每个活动期间的分钟数。
  4. (请注意,这假设事件可以持续多天:这真的可能吗?)

    另一种方法是在事件中设置一个滴答时钟,每次检查事件是否应该在那个时间计数,并在每次发现自己在相关时活动时递增(以秒或分钟为单位)期。这仍然需要帮助表,并且可能不太可审计(可能)。