拆分发生在一天边界内的事件

Splitting up events that occur over the day boundary

我有一个 table 事件,有开始时间和结束时间,有些事件的开始时间在午夜之前,结束时间在午夜之后。我想生成在午夜分界点拆分这些事件的输出,以便将它们计入各自的日期。

| EVENT_ID | START_TIME              | END_TIME                |
|----------|-------------------------|-------------------------|
| 1001     | 2021-02-21 14:00:00.000 | 2021-02-21 18:00:00.000 |
| 1002     | 2021-02-21 17:00:00.000 | 2021-02-22 03:00:00.000 |
| 1003     | 2021-02-21 18:00:00.000 | 2021-02-21 22:00:00.000 |
| 1004     | 2021-02-21 22:00:00.000 | 2021-02-22 07:00:00.000 | 

上面的table可以通过查询产生:

SELECT EVENT_ID,
       START_TIME,
       END_TIME
FROM EVENTS
WHERE START_TIME BETWEEN '2021-02-21 00:00:00.000' AND '2021-02-21 23:59:59.999'
;

我想要的输出将在午夜拆分跨越多天的事件:

| EVENT_ID | START_TIME              | END_TIME                |
|----------|-------------------------|-------------------------|
| 1001     | 2021-02-21 14:00:00.000 | 2021-02-21 18:00:00.000 |
| 1002     | 2021-02-21 17:00:00.000 | 2021-02-21 23:59:59.999 |
| 1002     | 2021-02-22 00:00:00.000 | 2021-02-22 03:00:00.000 |
| 1003     | 2021-02-21 18:00:00.000 | 2021-02-21 22:00:00.000 |
| 1004     | 2021-02-21 22:00:00.000 | 2021-02-21 23:59:59.999 |
| 1004     | 2021-02-22 00:00:00.000 | 2021-02-22 07:00:00.000 | 

如有任何帮助,我们将不胜感激。理想情况下,我想在没有功能或创建新的 tables.

的情况下生成它

请注意,我使用的是 SQL Server 2016

您可以为此使用递归 CTE:

with cte as (
      select event_id, start_time,
             (case when datediff(day, start_time, end_time) = 0 then end_time
                   else dateadd(day, 1, convert(date, start_time))
              end) as end_time,
             end_time as real_end_time
      from t
      union all
      select event_id, end_time,
             (case when dateadd(day, 1, convert(date, end_time)) > real_end_time
                   then real_end_time
                   else dateadd(day, 1, convert(date, end_time))
              end),
             real_end_time
       from cte
       where end_time < real_end_time
     )
select *
from cte;

Here 是一个 db<>fiddle.

使用 table 个数字

with t0(n) as (
 select n 
 from (
    values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
    ) t(n)
),nmbs as(
   select row_number() over(order by t1.n) - 1 n
   from t0 t1 cross join t0 t2 cross join t0 t3
)
select event_id, 
             case when n = 0 
                   then start_time
                   else dateadd(day, n, convert(date, start_time))
             end start_time,
             case when datediff(day, start_time, end_time) = n
                   then end_time
                   else dateadd(second, -1, dateadd(day, n + 1, convert(datetime, convert(date, start_time))))
             end as end_time
from Events
cross apply (
  select top (datediff(day, start_time, end_time) + 1) n 
  from nmbs) ns

下面的方法解决了START_TIME和END_TIME之间午夜的情况。上面的“期望输出”表示 START_TIME 和 END_TIME 之间只发生了一个午夜。

IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
CREATE TABLE #t ( Event_ID INT, START_TIME DATETIME2, END_TIME DATETIME2)
INSERT INTO #t (Event_ID, START_TIME, END_TIME)
VALUES
  ( 1001, '2021-02-21 14:00:00.000', '2021-02-21 18:00:00.000' )
, ( 1002, '2021-02-21 17:00:00.000', '2021-02-22 03:00:00.000' )
, ( 1003, '2021-02-21 18:00:00.000', '2021-02-21 22:00:00.000' )
, ( 1004, '2021-02-21 22:00:00.000', '2021-02-22 07:00:00.000' )

-- get original data plus midnight after START_TIME
IF OBJECT_ID('tempdb..#stage') IS NOT NULL DROP TABLE #stage
SELECT * 
, CONVERT(DATETIME2, CONVERT(DATE, DATEADD(DAY, 1, t.START_TIME))) d
INTO #stage
FROM #t t

-- get all rows
SELECT Event_ID, START_TIME
, CASE WHEN d > END_TIME THEN END_TIME ELSE d END END_TIME
FROM #stage

UNION ALL

-- get rows where midnight occurs between START_TIME and END_TIME
SELECT Event_ID
, CASE WHEN d > END_TIME THEN START_TIME ELSE d END  START_TIME
, END_TIME
FROM #stage 
WHERE d < END_TIME

ORDER BY Event_ID