假设我在SQL中定义了一个日期时间范围:
declare @STARTTIME datetime = '2014-09-10 13:00'
declare @ENDTIME datetime = '2014-09-12 13:00'
我有时间片:
declare @sliceStart time = '12:00'
declare @sliceEnd time = '16:00'
(见图)
我想计算这个切片在时间范围的持续时间内每天重叠多长时间。在第一天,重叠是3小时(从13到16),第二天是4小时,最后一天只有1小时。
范围实际上是在一个表中,并且有数百万个,我想计算考虑这些时间片的总持续时间。在我的例子中只有一个日期时间范围,但我目前最好的猜测是我需要创建一个我将应用于每一行的函数,因此该示例直接显示范围start和end作为变量。
答案 0 :(得分:1)
我知道可以采用更有效的方式,但您可以尝试以下方法。这是 Fiddle 。
DECLARE @STARTTIME DATETIME
SET @STARTTIME = '2014-09-10 13:00'
DECLARE @ENDTIME DATETIME
SET @ENDTIME = '2014-09-12 13:00'
DECLARE @sliceStart TIME
SET @sliceStart = '12:00'
DECLARE @sliceEnd TIME
SET @sliceEnd = '16:00'
;WITH SlicesCTE AS
(
SELECT @STARTTIME AS StartTime
UNION ALL SELECT DATEADD(hh,1,StartTime) AS StartTime
FROM SlicesCTE
WHERE StartTime < @ENDTIME
)
SELECT CONVERT(DATE,StartTime),
SUM
(
CASE
WHEN CONVERT(TIME,StartTime) >= @sliceStart
AND CONVERT(TIME,StartTime) < @sliceEnd
THEN 1
ELSE 0
END
)
FROM SlicesCTE
GROUP BY CONVERT(DATE,StartTime)
答案 1 :(得分:0)
这是一种方法。计算第一天和最后一天的重叠小时数,然后在中间添加完整的天数。
SQL Server使得它比必要的更难,因为很难添加时间。以下是使用小时作为差异的代码:
select t.*,
(case when cast(starttime as date) = cast(endtime as date)
then (case when sliceend > cast(starttime as time) and
slicestart < cast(endtime as time)
then datediff(hour,
(case when slicestart < cast(starttime as time) then cast(starttime as time) else slicestart end),
(case when sliceend > cast(endtime as time) then cast(endtime as time) else sliceend end)
)
else 0
end)
then datediff(hour,
(case when slicestart < cast(starttime as time) then cast(starttime as time) else slicestart end),
(case when sliceend > cast(endtime as time) then cast(endtime as time) else sliceend end)
)
else (datediff(day, starttime, endtime) - 1) * datediff(hour, slicestart, sliceend) +
(case when sliceend > cast(starttime as time)
then datediff(hour, (case when slicestart < cast(starttime as time) then cast(starttime as time) else slicestart end),
sliceend)
else 0
end) +
(case when slicestart < cast(endtime as time)
then datediff(hour, slicestart,
(case when sliceend > cast(endtime as time) then cast(endtime as time) else sliceend end)
)
else 0
end)
end) as hours
from (select cast('2014-09-10 13:00' as datetime) as starttime,
cast('2014-09-12 13:00' as datetime) as endtime,
cast('12:00' as time) as slicestart,
cast('16:00' as time) as sliceend
) t;
Here是查询的SQL小提琴。
答案 2 :(得分:0)
[sss_M_M_M_M_M_M_M_M_eee]
|- - - -|- - - -|- - - -|- - - -|
将每个“范围”视为以下集合:
和
开始时段以“范围”开始日期的时间已知,结束时段以“范围”结束日期的时间已知。如果“范围”开始日期和结束日期不同,则中间期间> 0并由(DATEDIFF(DAY, starttime, endtime) - 1)
在下面的查询中,通过使用cross apply
,我们可以提供一些所需的计算列别名,可以在查询的其他位置重用。因此,通过使用在第一个交叉应用中建立的别名来简化第二次交叉应用。
第二个十字架中的案例表达适用于条件1,2和&amp; 3.前面提到过:
if range start & end are the same day (middle = 0)
then calculate any overlap of slice and range
else (middle > 0)
1 overlap for each day in the middle
+ any overlap in the start period (1 or less)
+ any overlap in the end period (1 or less)
注意:为了满足切片和/或范围中的部分小时数,将未与完整小时分钟对齐的开始时间/结束时间用作度量单位;因此,切片同样被视为分钟,但是如果需要,我们可以恢复到几个小时,并且还可以计算“重叠”的数量(实际上是“重叠等价物”)。
示例结果:
| DATE_START | DATE_END |TIME_START| TIME_END | SLICE_MIN | OVERLAP_MIN | OVERLAPS | OVERLAP_HOURS |
|------------|------------|----------|----------|-----------|-------------|----------|---------------|
| 2014-09-10 | 2014-09-12 | 13:00:00 | 13:00:00 | 240 | 480 | 2 | 8 |
| 2014-09-10 | 2014-09-12 | 12:00:00 | 16:00:00 | 240 | 720 | 3 | 12 |
| 2014-09-10 | 2014-09-12 | 13:15:00 | 17:00:00 | 240 | 645 | 2.6875 | 10.75 |
| 2014-08-10 | 2014-09-12 | 13:15:00 | 17:00:00 | 240 | 8085 | 33.6875 | 134.75 |
查询:(假设与切片进行比较的表中存在“范围”)
DECLARE @sliceStart TIME
SET @sliceStart = '12:00'
DECLARE @sliceEnd TIME
SET @sliceEnd = '16:00'
SELECT
ca1.*
, ca2.*
, ca2.overlap_min / (ca1.slice_min * 1.0) AS overlaps
, ca2.overlap_min / 60.0 AS overlap_hours
FROM Ranges
cross apply (
select
CAST(starttime AS date) AS date_start
, CAST(endtime AS date) AS date_end
, CAST(starttime AS time) AS time_start
, CAST(endtime AS time) AS time_end
, DATEDIFF(MINUTE, @slicestart, @sliceend) AS slice_min
) ca1
cross apply (
select
(CASE
WHEN ca1.date_start = ca1.date_end THEN
(CASE
WHEN @sliceend > ca1.time_start AND @slicestart < ca1.time_end THEN DATEDIFF(MINUTE,
(CASE
WHEN @slicestart < ca1.time_start THEN ca1.time_start ELSE @slicestart END),
(CASE
WHEN @sliceend > ca1.time_end THEN ca1.time_end ELSE @sliceend END)
)
ELSE 0 END)
ELSE (DATEDIFF(DAY, starttime, endtime) - 1) * ca1.slice_min
+ (CASE
WHEN @sliceend > ca1.time_start THEN DATEDIFF(MINUTE, (CASE
WHEN @slicestart < ca1.time_start THEN ca1.time_start ELSE @slicestart END),
@sliceend)
ELSE 0 END)
+ (CASE
WHEN @slicestart < ca1.time_end THEN DATEDIFF(MINUTE, @slicestart,
(CASE
WHEN @sliceend > ca1.time_end THEN ca1.time_end ELSE @sliceend END)
)
ELSE 0
END)
END) AS overlap_min
) as ca2
;
顺便说一句,这种方法是计算工作日的变体