如何在两个日期之间获得经过的时间,忽略一些时间范围?

时间:2015-11-10 21:38:10

标签: sql sql-server tsql

我有一个包含DATETIME列'Start'和DATETIME列'End'的表。我想在开始和结束之间返回分钟数(结束总是在开始之后)。通常我只使用'DateDiff()',但这次我需要排除另一个日期范围。例如 - 从星期二上午9点到星期三下午6点,每周都应该被忽略。

如果一行有一个星期二的开始时间是早上8点,周三结束时间是晚上7点 - 经过的时间应该是两个小时(120分钟) - 因为忽略了日期范围。

我很难找到一个体面的方式来做这件事,而我在线搜索并没有找到我正在寻找的东西。有人可以帮助我吗?

4 个答案:

答案 0 :(得分:2)

试试这个:

--total time span to calculate difference
DECLARE @StartDate DATETIME = '2015-11-10 8:00:00AM',
        @EndDate DATETIME = '2015-11-11 7:00:00PM'

--get the day of week (-1 because sunday is counted as first weekday)
DECLARE @StartDayOfWeek INT = (SELECT DATEPART(WEEKDAY, @StartDate)) -1
DECLARE @EndDayOfWeek INT = (SELECT DATEPART(WEEKDAY, @EndDate)) -1

--set the time span to exclude
DECLARE @InitialDOWToExclude TINYINT = 2
DECLARE @InitialTODToExclude VARCHAR(100) = '9:00:00 AM'

DECLARE @EndDOWToExclude TINYINT = 3
DECLARE @EndTODToExclude VARCHAR(100) = '6:00:00 PM'

--this will be the final output in hours
DECLARE @ElapsedHours INT = (SELECT DATEDIFF(HOUR, @StartDate, @EndDate))

DECLARE @WeeksBetween INT = (SELECT DATEDIFF(WEEK, @StartDate, @EndDate))
DECLARE @Iterator INT = 0
WHILE (@Iterator <= @WeeksBetween)
BEGIN
    DECLARE @InitialDaysBetween INT = @StartDayOfWeek - @InitialDOWToExclude
    DECLARE @StartDateToExclude DATETIME = (SELECT DATEADD(DAY, @InitialDaysBetween, DATEADD(WEEK, @Iterator, @StartDate)))
    SET @StartDateToExclude =CAST(DATEPART(YEAR, @StartDateToExclude) AS VARCHAR(100))
                              + CAST(DATEPART(MONTH, @StartDateToExclude) AS VARCHAR(100))
                              + CAST(DATEPART(DAY, @StartDateToExclude) AS VARCHAR(100))
                              + ' '
                              + CAST(@InitialTODToExclude AS VARCHAR(100))

    DECLARE @EndDaysBetween INT = @EndDayOfWeek - @EndDOWToExclude 
    DECLARE @EndDateToExclude DATETIME = (SELECT DATEADD(DAY, @EndDaysBetween, DATEADD(WEEK, @Iterator, @EndDate)))
    SET @EndDateToExclude =CAST(DATEPART(YEAR, @EndDateToExclude) AS VARCHAR(100))
                              + CAST(DATEPART(MONTH, @EndDateToExclude) AS VARCHAR(100))
                              + CAST(DATEPART(DAY, @EndDateToExclude) AS VARCHAR(100))
                              + ' '
                              + CAST(@EndTODToExclude AS VARCHAR(100))

    SET @ElapsedHours = @ElapsedHours - DATEDIFF(HOUR, @StartDateToExclude, @EndDateToExclude)

    SET @Iterator = @Iterator + 1
END

SELECT @ElapsedHours

答案 1 :(得分:1)

这可能会让你非常接近......

DECLARE @Table1 TABLE ([Id] INT, [Start] DATETIME, [End] DATETIME)
INSERT INTO @Table1 VALUES
(1, '2015-11-08 00:00:00', '2015-11-10 21:45:38'),
(2, '2015-11-09 00:00:00', '2015-11-11 21:45:38')
;

-- hours to exclude
WITH excludeCTE AS 
(
    SELECT * 
    FROM (VALUES('Tuesday', 9, 0), ('Wednesday', 0, 0)) AS T([Day], [Hour], [Amount])
    UNION ALL 
    SELECT [Day], [Hour] + 1, [Amount]  
    FROM excludeCTE
    WHERE  ([Day] = 'Tuesday' AND [Hour] < 23) OR ([Day] = 'Wednesday' AND [Hour] < 18)
),
-- all hours between start and end
dateCTE AS
(
    SELECT  [Id], 
            [Start], 
            [End], 
            DATENAME(weekday, [Start])[Day],
            DATENAME(hour, [Start])[Hour]
    FROM    @Table1 t
    UNION ALL 
    SELECT  cte.[Id], 
            DATEADD(HOUR, 1, cte.[Start]), 
            cte.[End], 
            DATENAME(weekday, DATEADD(HOUR, 1, cte.[Start]))[Day],
            DATENAME(hour, DATEADD(HOUR, 1, cte.[Start]))[Hour]
    FROM    @Table1 t
            JOIN dateCTE cte ON t.Id = cte.Id
    WHERE   DATEADD(HOUR, 1, cte.[Start]) <= t.[End]

)
SELECT  t.[Id], 
        t.[Start], 
        t.[End], 
        SUM(COALESCE(e.[Amount], 1)) [Hours]
FROM    @Table1 t
        INNER JOIN dateCTE d ON t.[Id] = d.[Id]
        LEFT JOIN excludeCTE e ON d.[Day] = e.[Day] AND d.[Hour] = e.[Hour]
GROUP BY t.[Id], 
        t.[Start], 
        t.[End]
OPTION (MAXRECURSION 0) -- allow more than 100 hours 

答案 2 :(得分:1)

设置附加约束,即任何两个日期之间只能有一个被排除的范围

CREATE TABLE worktable (
  _Id INT
, _Start DATETIME
, _End DATETIME
);
INSERT INTO worktable VALUES
  (1, '2015-11-09 00:00:00', '2015-11-09 00:45:00') -- Start and End before excluded range
, (2, '2015-11-09 00:00:00', '2015-11-11 21:45:00') -- Start before, End after
, (3, '2015-11-09 00:00:00', '2015-11-10 21:00:00') -- Start before, End between
, (4, '2015-11-10 10:00:00', '2015-11-11 10:00:00') -- Start between, End between
, (5, '2015-11-10 10:00:00', '2015-11-11 21:45:00') -- Start between, End after

With getDates As (
  SELECT _Id
       , a = _Start
       , b = _End
       , c = DATEADD(hh, 9
           , DATEADD(DAY,DATEDIFF(DAY, 0, _Start) / 7 * 7
           + 7 * Cast(Sign(1 - DatePart(dw, _Start)) + 1 as bit), 1))
       , d = DATEADD(hh, 18
           , DATEADD(DAY,DATEDIFF(DAY, 0, _Start) / 7 * 7
           + 7 * Cast(Sign(1 - DatePart(dw, _Start)) + 1 as bit), 2))
  FROM   worktable
), getDiff As (
  SELECT c_a = DATEDIFF(mi, a, c)
       , c_b = DATEDIFF(mi, b, c)
       , b_d = DATEDIFF(mi, d, b)
       , a, b, c, d, _id
  FROM   getDates
)
Select _id
     , (c_a + ABS(c_a)) / 2
     - (c_b + ABS(c_b)) / 2
     + (b_d + ABS(b_d)) / 2
FROM   getDiff;

c是开始日期后的第一个星期二的日期(Find the next occurance of a day of the week in SQL)您可能需要根据DATEFIRST调整最后一个值

dc同一周开始日期后第一个星期三的日期

如果Cast(Sign(a - b) + 1 as bit)大于或等于a,则

b为1,否则为

如果不是负数,则

(x + ABS(x)) / 2x,否则为0

鉴于获得排除范围的经过时间的公式为:

 + (Exclusion Start - Start) If (Start < Exclusion Start)
 - (Exclusion Start - End) If (End < Exclusion Start) 
 + (End - Exclusion End) If (Exclusion End < End)

答案 3 :(得分:1)

-- excluded range (weekday numbers run from 1 to 7)
declare @x datetime = /*ignore*/ '1900012' + /*start day # and time*/ '3 09:00am';
declare @y datetime = /*ignore*/ '1900012' + /*  end day # and time*/ '4 06:00pm';

-- normalize date to 1900-01-21, which was a Sunday
declare @s datetime =
    dateadd(day, 19 + datepart(weekday, @start), cast(cast(@start as time) as datetime));
declare @e datetime =
    dateadd(day, 19 + datepart(weekday, @end), cast(cast(@end as time) as datetime));

-- split range into two parts, one before @x and the other after @y
-- each part collapses to zero when @s and @e respectively fall between @x and @y
select (
    datediff(second, -- diff in minutes would truncate so count seconds
        case when @s < @x then @s else @x end, -- minimum of @s, @x
        case when @e < @x then @e else @x end  -- minimum of @e, @x
    ) +
    datediff(second,
        case when @s > @y then @s else @y end, -- maximum of @s, @y
        case when @e > @y then @e else @y end  -- maximum of @e, @y
    )
) / 60; -- convert seconds to minutes, truncating with integer division

我瞥了一眼之前的答案,我认为肯定会有更直接和优雅的东西。也许这比较容易理解,而且与某些解决方案相比,一个明显的优势就是改变排除的范围是微不足道的,并且范围不必限于一天。

我假设您的约会时间从不超过一个常规日历周。尽管如此,扩展它以解决更多问题并不困难。一种方法是处理开始和结束部分周以及中间的整周。

想象一下,您的开始时间是上午8:59:30,结束时间是下午6:00:30。在这种情况下,我想知道你在减去9-6区块之后想要在每一侧积累半分钟以获得整整一分钟。如果你使用datediff(minute, ...),你将截断部分分钟并且永远不会有机会将它们加在一起:这就是为什么我计算秒数然后在结尾处除以60。当然,如果你只是处理整个会议记录,那么你就不需要这样做了。

我在某种程度上任意选择了我的参考日期。起初我认为在日历上查看一个真实而方便的日期可能会很方便,但最终它只是在周日才真正重要。所以我决定在第一个星期天落在以数字1结束的日期。

请注意,该解决方案还依赖于datefirst设置为星期日。如有必要,可以调整或更便携。