By using db<>fiddle, you agree to license everything you submit by Creative Commons CC0.
-- Create the Tasks table
CREATE TABLE Tasks (
TaskID INT PRIMARY KEY,
Technician NVARCHAR(50),
StartDate DATETIME,
EndDate DATETIME
);
-- Insert sample data into the Tasks table
INSERT INTO Tasks (TaskID, Technician, StartDate, EndDate)
VALUES
(1, 'Tech1', '2023-10-30 08:05:00', '2023-10-30 08:25:00'),
(2, 'Tech1', '2023-10-30 08:20:00', '2023-10-30 08:35:00'),
(3, 'Tech2', '2023-10-30 08:30:00', '2023-10-30 09:10:00'),
(4, 'Tech1', '2023-10-30 09:05:00', '2023-10-30 09:45:00'),
(5, 'Tech3', '2023-10-30 08:10:00', '2023-10-30 08:20:00'),
(6, 'Tech3', '2023-10-30 08:30:00', '2023-10-30 08:45:00');
6 rows affected
-- ChatGPT's answer (after it got it horribly wrong first)
WITH TimeIntervals AS (
SELECT
Technician,
TaskID,
StartDate,
EndDate,
DATEADD(MINUTE, (DATEDIFF(MINUTE, 0, StartDate) / 15) * 15, 0) AS IntervalStart,
DATEADD(MINUTE, ((DATEDIFF(MINUTE, 0, StartDate) / 15) + 1) * 15, 0) AS IntervalEnd
FROM
Tasks
UNION ALL
SELECT
Technician,
TaskID,
StartDate,
EndDate,
DATEADD(MINUTE, 15, IntervalStart),
DATEADD(MINUTE, 15, IntervalEnd)
FROM
TimeIntervals
WHERE
IntervalEnd < EndDate
)
, WorkIntervals AS (
SELECT
Technician,
TaskID,
IntervalStart,
IntervalEnd,
CASE
WHEN StartDate > IntervalStart THEN StartDate
ELSE IntervalStart
END AS WorkStart,
CASE
WHEN EndDate < IntervalEnd THEN EndDate
Technician | IntervalStart | IntervalEnd | WorkedMinutes |
---|---|---|---|
Tech1 | 2023-10-30 08:00:00.000 | 2023-10-30 08:15:00.000 | 10 |
Tech1 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 | 20 |
Tech1 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 | 5 |
Tech1 | 2023-10-30 09:00:00.000 | 2023-10-30 09:15:00.000 | 10 |
Tech1 | 2023-10-30 09:15:00.000 | 2023-10-30 09:30:00.000 | 15 |
Tech1 | 2023-10-30 09:30:00.000 | 2023-10-30 09:45:00.000 | 15 |
Tech2 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 | 15 |
Tech2 | 2023-10-30 08:45:00.000 | 2023-10-30 09:00:00.000 | 15 |
Tech2 | 2023-10-30 09:00:00.000 | 2023-10-30 09:15:00.000 | 10 |
Tech3 | 2023-10-30 08:00:00.000 | 2023-10-30 08:15:00.000 | 5 |
Tech3 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 | 5 |
Tech3 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 | 15 |
-- My answer... starting with generating a Time Blocks table containing every 15 minutes since 2000-01-01.
WITH FifteenMinuteBlocks AS
(
SELECT value * 15 AS Fifteens
FROM generate_series(0, 1000000) t
)
SELECT DATEADD(minute, b.Fifteens, '2000-01-01') AS TimeBlockStart
, DATEADD(minute, b.Fifteens + 15, '2000-01-01') AS TimeBlockEnd
INTO #TimeBlocks
FROM FifteenMinuteBlocks b
;
1000001 rows affected
SELECT TOP 10 *
FROM #TimeBlocks;
TimeBlockStart | TimeBlockEnd |
---|---|
2000-01-01 00:00:00.000 | 2000-01-01 00:15:00.000 |
2000-01-01 00:15:00.000 | 2000-01-01 00:30:00.000 |
2000-01-01 00:30:00.000 | 2000-01-01 00:45:00.000 |
2000-01-01 00:45:00.000 | 2000-01-01 01:00:00.000 |
2000-01-01 01:00:00.000 | 2000-01-01 01:15:00.000 |
2000-01-01 01:15:00.000 | 2000-01-01 01:30:00.000 |
2000-01-01 01:30:00.000 | 2000-01-01 01:45:00.000 |
2000-01-01 01:45:00.000 | 2000-01-01 02:00:00.000 |
2000-01-01 02:00:00.000 | 2000-01-01 02:15:00.000 |
2000-01-01 02:15:00.000 | 2000-01-01 02:30:00.000 |
-- Basically, we're just joining the TimeBlocks table to our data.
SELECT t.*
, tb.*
FROM Tasks t
INNER JOIN #TimeBlocks tb
ON ( t.StartDate < tb.TimeBlockEnd
AND t.EndDate > tb.TimeBlockStart
);
TaskID | Technician | StartDate | EndDate | TimeBlockStart | TimeBlockEnd |
---|---|---|---|---|---|
1 | Tech1 | 2023-10-30 08:05:00.000 | 2023-10-30 08:25:00.000 | 2023-10-30 08:00:00.000 | 2023-10-30 08:15:00.000 |
1 | Tech1 | 2023-10-30 08:05:00.000 | 2023-10-30 08:25:00.000 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 |
2 | Tech1 | 2023-10-30 08:20:00.000 | 2023-10-30 08:35:00.000 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 |
2 | Tech1 | 2023-10-30 08:20:00.000 | 2023-10-30 08:35:00.000 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 |
3 | Tech2 | 2023-10-30 08:30:00.000 | 2023-10-30 09:10:00.000 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 |
3 | Tech2 | 2023-10-30 08:30:00.000 | 2023-10-30 09:10:00.000 | 2023-10-30 08:45:00.000 | 2023-10-30 09:00:00.000 |
3 | Tech2 | 2023-10-30 08:30:00.000 | 2023-10-30 09:10:00.000 | 2023-10-30 09:00:00.000 | 2023-10-30 09:15:00.000 |
4 | Tech1 | 2023-10-30 09:05:00.000 | 2023-10-30 09:45:00.000 | 2023-10-30 09:00:00.000 | 2023-10-30 09:15:00.000 |
4 | Tech1 | 2023-10-30 09:05:00.000 | 2023-10-30 09:45:00.000 | 2023-10-30 09:15:00.000 | 2023-10-30 09:30:00.000 |
4 | Tech1 | 2023-10-30 09:05:00.000 | 2023-10-30 09:45:00.000 | 2023-10-30 09:30:00.000 | 2023-10-30 09:45:00.000 |
5 | Tech3 | 2023-10-30 08:10:00.000 | 2023-10-30 08:20:00.000 | 2023-10-30 08:00:00.000 | 2023-10-30 08:15:00.000 |
5 | Tech3 | 2023-10-30 08:10:00.000 | 2023-10-30 08:20:00.000 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 |
6 | Tech3 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 |
-- And then we need to calculate the number of minutes that are within our time block.
SELECT t.TaskID
, t.Technician
, DATEDIFF(minute
, IIF(t.StartDate < tb.TimeBlockStart, tb.TimeBlockStart, t.StartDate)
, IIF(t.EndDate > tb.TimeBlockEnd, tb.TimeBlockEnd, t.EndDate)
) AS Minutes
, t.StartDate
, t.EndDate
, tb.TimeBlockStart
, tb.TimeBlockEnd
FROM Tasks t
INNER JOIN #TimeBlocks tb
ON ( t.StartDate < tb.TimeBlockEnd
AND t.EndDate > tb.TimeBlockStart
);
TaskID | Technician | Minutes | StartDate | EndDate | TimeBlockStart | TimeBlockEnd |
---|---|---|---|---|---|---|
1 | Tech1 | 10 | 2023-10-30 08:05:00.000 | 2023-10-30 08:25:00.000 | 2023-10-30 08:00:00.000 | 2023-10-30 08:15:00.000 |
1 | Tech1 | 10 | 2023-10-30 08:05:00.000 | 2023-10-30 08:25:00.000 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 |
2 | Tech1 | 10 | 2023-10-30 08:20:00.000 | 2023-10-30 08:35:00.000 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 |
2 | Tech1 | 5 | 2023-10-30 08:20:00.000 | 2023-10-30 08:35:00.000 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 |
3 | Tech2 | 15 | 2023-10-30 08:30:00.000 | 2023-10-30 09:10:00.000 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 |
3 | Tech2 | 15 | 2023-10-30 08:30:00.000 | 2023-10-30 09:10:00.000 | 2023-10-30 08:45:00.000 | 2023-10-30 09:00:00.000 |
3 | Tech2 | 10 | 2023-10-30 08:30:00.000 | 2023-10-30 09:10:00.000 | 2023-10-30 09:00:00.000 | 2023-10-30 09:15:00.000 |
4 | Tech1 | 10 | 2023-10-30 09:05:00.000 | 2023-10-30 09:45:00.000 | 2023-10-30 09:00:00.000 | 2023-10-30 09:15:00.000 |
4 | Tech1 | 15 | 2023-10-30 09:05:00.000 | 2023-10-30 09:45:00.000 | 2023-10-30 09:15:00.000 | 2023-10-30 09:30:00.000 |
4 | Tech1 | 15 | 2023-10-30 09:05:00.000 | 2023-10-30 09:45:00.000 | 2023-10-30 09:30:00.000 | 2023-10-30 09:45:00.000 |
5 | Tech3 | 5 | 2023-10-30 08:10:00.000 | 2023-10-30 08:20:00.000 | 2023-10-30 08:00:00.000 | 2023-10-30 08:15:00.000 |
5 | Tech3 | 5 | 2023-10-30 08:10:00.000 | 2023-10-30 08:20:00.000 | 2023-10-30 08:15:00.000 | 2023-10-30 08:30:00.000 |
6 | Tech3 | 15 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 | 2023-10-30 08:30:00.000 | 2023-10-30 08:45:00.000 |
-- Then we SUM the minutes for each tech within the time block.
SELECT
t.Technician
, tb.TimeBlockStart
, SUM(DATEDIFF(minute
, IIF(t.StartDate < tb.TimeBlockStart, tb.TimeBlockStart, t.StartDate)
, IIF(t.EndDate > tb.TimeBlockEnd, tb.TimeBlockEnd, t.EndDate)
)) AS Minutes
FROM Tasks t
INNER JOIN #TimeBlocks tb
ON ( t.StartDate < tb.TimeBlockEnd
AND t.EndDate > tb.TimeBlockStart
)
GROUP BY t.Technician
, tb.TimeBlockStart
ORDER BY t.Technician
, tb.TimeBlockStart
;
Technician | TimeBlockStart | Minutes |
---|---|---|
Tech1 | 2023-10-30 08:00:00.000 | 10 |
Tech1 | 2023-10-30 08:15:00.000 | 20 |
Tech1 | 2023-10-30 08:30:00.000 | 5 |
Tech1 | 2023-10-30 09:00:00.000 | 10 |
Tech1 | 2023-10-30 09:15:00.000 | 15 |
Tech1 | 2023-10-30 09:30:00.000 | 15 |
Tech2 | 2023-10-30 08:30:00.000 | 15 |
Tech2 | 2023-10-30 08:45:00.000 | 15 |
Tech2 | 2023-10-30 09:00:00.000 | 10 |
Tech3 | 2023-10-30 08:00:00.000 | 5 |
Tech3 | 2023-10-30 08:15:00.000 | 5 |
Tech3 | 2023-10-30 08:30:00.000 | 15 |