SQL Server 需要一个持续时间并将时间解析为该持续时间的小时部分

问题描述

我再次发布 this question 是因为项目已更改并且之前的答案没有返回所需的结果。救护车和消防车有紧急情况发生时的调度时间和紧急情况宣布结束时的结束时间。

活动 1 于 2021 年 5 月 1 日 10:17:33 开始,并于 2021 年 5 月 1 日 10:33:41 结束。

活动 2 于 2021 年 5 月 1 日 11:50:52 开始,并于 2021 年 5 月 1 日 13:18:21 结束。

我想解析从开始到结束的时间量,并在它发生时将其放入小时部分。例如;事件 1 从 10:17 开始,在 10:33 结束。它将在当天的 10:00 小时部分放置 16 分钟。事件 2 将在 11:00 小时部分放置 10 分钟,在 12:00 小时部分放置 60 分钟,在 13:00 小时部分放置 18 分钟。将分钟放在事件发生的小时内。

结果应如下所示。虽然我很灵活。例如,如果在结果中不能返回卡车的名称,因为如果有 EventID,我可以关联回原始表。

事件ID 救护车 EventDayOfYear 活动时间 分配的分钟数
1 Medic10 121 10 16
1 Medic10 121 11 10
2 Ladder73 121 11 10
2 Ladder73 121 12 60
2 Ladder73 121 13 18
3 Engine41 121 13 33
3 Engine41 121 14 21
4 Medic83 121 15 32
4 Medic83 121 16 5
5 救援32 121 16 33
6 Medic09 121 23 16
6 Medic09 122 0 39
7 Engine18 121 23 28
7 Engine18 122 0 60
7 Engine18 122 1 34
8 救援63 122 0 35

以下 sql 代码几乎可以提供正确的结果。 但不重叠天。有许多紧急事件从 2300 小时开始,一直持续到第二天 0300 小时。

DECLARE @tempFireEvents TABLE
(
  EventID INT NOT NULL,Apparatus VARCHAR(10) NOT NULL,StartDateTime DATETIME NOT NULL,EndDateTime DATETIME NOT NULL,DurationInSeconds INT NOT NULL
)

INSERT INTO @tempFireEvents
VALUES
  (1,'Medic10','may 1,2021 10:17:33',2021 10:33:41',968) /*This event is entirely within 1000 hours*/,(2,'Ladder73',2021 11:50:52',2021 13:18:21',5249) /*This event spans 1100,1200 and 1300 hours*/,(3,'Engine41',2021 13:27:17',2021 14:21:18',3241) /*This event overlaps 1300 and 1400 hours*/,(4,'Medic83',2021 15:28:08',2021 16:05:48',2260) /*This event overlaps 1500 and 1600 hours*/,(5,'Rescue32',2021 16:20:43',2021 16:53:28',1965) /*This event is entirely within the 1600 hour part*/,(6,'Medic09',2021 23:44:06','may 2,2021 00:39:52',3346) /*Notice this overlaps the 2300 and 0000 hours into the following day*/,(7,'Engine18',2021 23:32:58',2021 01:34:13',7275) /*Notice this overlaps the 2300,0000 and 0100 hours into the following day*/,(8,'Rescue63',2021 00:17:45',2021 00:52:09',2064) /*Notice this is the 00 hour of the day and does not show in the results*/
;

WITH AllHours AS
(
SELECT 1 AS hourInt
UNION ALL
SELECT hourInt + 1
FROM AllHours
WHERE hourInt < 23
),Combined AS
(
SELECT 
  T.EventID,H.hourInt,CASE WHEN DATEPART(HOUR,T.StartDateTime) = H.hourInt THEN 1 ELSE 0 END AS isstart,T.EndDateTime) = H.hourInt THEN 1 ELSE 0 END AS isEnd,T.StartDateTime,T.EndDateTime
FROM @tempFireEvents AS [T]
JOIN AllHours AS [H] ON H.hourInt BETWEEN DATEPART(HOUR,T.StartDateTime) AND DATEPART(HOUR,T.EndDateTime)
)

SELECT 
  EventID,hourInt,CASE WHEN isstart = 1 AND isEnd = 0 THEN 60 - DATEPART(MINUTE,StartDateTime)
  WHEN isstart = 0 AND isEnd = 1 THEN DATEPART(MINUTE,EndDateTime)
  WHEN isstart = 1 AND isEnd = 1 THEN DATEPART(MINUTE,EndDateTime) -  DATEPART(MINUTE,StartDateTime)
  ELSE 60
  END AS MinutesInThisHour
FROM Combined
ORDER BY EventID ASC,hourint ASC
;

我怀疑 sql Server 可能不是实现目标的最佳方法。它可能需要用 Python 编写,带有增量和减量以及计数器。

如果有帮助,我有一个日历表,看起来像:

May 1,2021 00:00:00
May 1,2021 01:00:00
May 1,2021 02:00:00
May 1,2021 03:00:00
May 1,2021 04:00:00
May 1,2021 05:00:00
May 1,2021 06:00:00

日历表对解决这个问题有用吗?

解决方法

CREATE TABLE tempFireEvents
(
EventID VARCHAR(8) NOT NULL,StartDateTime DATETIME NOT NULL,EndDateTime DATETIME NOT NULL
)

INSERT INTO tempFireEvents
VALUES
('fire0001','november 1,2018 10:45:00','november 2,2018 11:30:00'),('fire0002',2018 11:50:00',2018 13:10:00'),('fire0003',2018 13:20:00',2018 14:20:00'),('fire0004',2018 15:25:00',2018 16:05:00'),('fire0005',2018 16:20:00',2018 17:00:00'),('fire0006',2018 17:01:00');

select e.*,hr.ld,60 - case when e.startdatetime > hr.ld then datepart(minute,e.startdatetime) else 0 end
   + case when e.enddatetime < hr.ud then datepart(minute,e.enddatetime)-60 else 0 end as allocatedminutes
from tempFireEvents as e
cross apply
(
  select
    dateadd(hour,datepart(hour,e.startdatetime)+t.rn-1,cast(cast(e.startdatetime as date) as datetime)) as ld,dateadd(hour,e.startdatetime)+t.rn,cast(cast(e.startdatetime as date) as datetime)) as ud,rn
  from
  (
    -- a tally,max 100 rows .. max 100 hours duration
    select top (1+datediff(hour,e.startdatetime,dateadd(minute,-1,e.enddatetime))) row_number() over(order by @@spid) as rn
    from (values(1),(1),(1)) as a(n)
    cross join (values(1),(1)) as b(n)
  ) as t
) as hr;