SQL 服务器获取持续时间并将时间解析为该持续时间的小时部分

SQL Server take a time duration and parse the time into the hour parts of that duration

我再次发帖 因为项目已经更改并且以前的答案没有 return 想要的结果。救护车和消防车有紧急情况发生时的出动时间和宣布紧急情况结束的结束时间。

活动 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 分钟。将分钟记入事件发生的小时数。

结果应如下所示。虽然我很灵活。例如,如果不能在结果中 return 编辑卡车的名称,那也没关系,因为如果 EventID 在那里,我可以关联回原来的 table。

EventID Ambulance EventDayOfYear EventHour MinutesAllocated
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 Rescue32 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 Rescue63 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', 'may 1, 2021 10:33:41', 968) /*This event is entirely within 1000 hours*/
, (2, 'Ladder73', 'may 1, 2021 11:50:52', 'may 1, 2021 13:18:21', 5249) /*This event spans 1100, 1200 and 1300 hours*/
, (3, 'Engine41', 'may 1, 2021 13:27:17', 'may 1, 2021 14:21:18', 3241) /*This event overlaps 1300 and 1400 hours*/
, (4, 'Medic83', 'may 1, 2021 15:28:08', 'may 1, 2021 16:05:48', 2260) /*This event overlaps 1500 and 1600 hours*/
, (5, 'Rescue32', 'may 1, 2021 16:20:43', 'may 1, 2021 16:53:28', 1965) /*This event is entirely within the 1600 hour part*/
, (6, 'Medic09', 'may 1, 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', 'may 1, 2021 23:32:58', 'may 2, 2021 01:34:13', 7275) /*Notice this overlaps the 2300, 0000 and 0100 hours into the following day*/
, (8, 'Rescue63', 'may 2, 2021 00:17:45', 'may 2, 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
, CASE WHEN DATEPART(HOUR, 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 服务器可能不是实现目标的最佳方法。可能需要写成Python加减加计数器

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

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

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

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', 'november 1, 2018 11:50:00', 'november 1, 2018 13:10:00'),
('fire0003', 'november 1, 2018 13:20:00', 'november 1, 2018 14:20:00'),
('fire0004', 'november 1, 2018 15:25:00', 'november 1, 2018 16:05:00'),
('fire0005', 'november 1, 2018 16:20:00', 'november 2, 2018 17:00:00'),
('fire0006', 'november 1, 2018 16:20:00', 'november 1, 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, datepart(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),(1),(1),(1),(1),(1),(1),(1)) as a(n)
    cross join (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) as b(n)
  ) as t
) as hr;