我有一个包含DATETIME列'Start'和DATETIME列'End'的表。我想在开始和结束之间返回分钟数(结束总是在开始之后)。通常我只使用'DateDiff()',但这次我需要排除另一个日期范围。例如 - 从星期二上午9点到星期三下午6点,每周都应该被忽略。
如果一行有一个星期二的开始时间是早上8点,周三结束时间是晚上7点 - 经过的时间应该是两个小时(120分钟) - 因为忽略了日期范围。
我很难找到一个体面的方式来做这件事,而我在线搜索并没有找到我正在寻找的东西。有人可以帮助我吗?
答案 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调整最后一个值
d
是c
同一周开始日期后第一个星期三的日期
Cast(Sign(a - b) + 1 as bit)
大于或等于a
,则 b
为1,否则为
(x + ABS(x)) / 2
为x
,否则为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
设置为星期日。如有必要,可以调整或更便携。