SQL计算日期时间范围的每一天的时间片

时间:2014-09-18 11:39:07

标签: sql sql-server datetime

假设我在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'

(见图) enter image description here

我想计算这个切片在时间范围的持续时间内每天重叠多长时间。在第一天,重叠是3小时(从13到16),第二天是4小时,最后一天只有1小时。

范围实际上是在一个表中,并且有数百万个,我想计算考虑这些时间片的总持续时间。在我的例子中只有一个日期时间范围,但我目前最好的猜测是我需要创建一个我将应用于每一行的函数,因此该示例直接显示范围start和end作为变量。

3 个答案:

答案 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]
|- - - -|- - - -|- - - -|- - - -|

将每个“范围”视为以下集合:

  • 一个开始时期(sss)
  • 结束时期(eee)
  • 中期持续时间为零或更多完整日期(_M_M_M _)

  1. 切片可能在之前,或重叠,或在开始期间内
  2. 切片可能在结束期间,或重叠,或在结束期之后
  3. 中间的每一天都会出现切片
  4. 开始时段以“范围”开始日期的时间已知,结束时段以“范围”结束日期的时间已知。如果“范围”开始日期和结束日期不同,则中间期间> 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
    ;
    

    See this SQLFiddle demo


    顺便说一句,这种方法是计算工作日的变体