When asking a question like this it's important to provide easily reproducible DDL/DML. This does not include pictures of data as it cannot be copied easily, and does not describe the data types that are in use. Consider:
DECLARE @Table TABLE (CSN INT, BloodPressure NVARCHAR(7), ReadingDateTime DATETIME);
INSERT INTO @Table (CSN, BloodPressure, ReadingDateTime) VALUES
(614, '147/87', '2022-8-22 13:35:00'), (614, '150/90', '2022-8-22 13:50:00'), (614, '149/89', '2022-8-22 14:05:00'),
(614, '148/88', '2022-8-22 16:20:00'), (297, '140/80', '2022-8-22 12:50:00'), (297, '139/79', '2022-8-22 13:05:00'),
(297, '138/78', '2022-8-22 13:20:00'), (297, '137/77', '2022-8-22 13:35:00'), (297, '140/80', '2022-8-22 13:50:00'),
(614, '148/88', '2022-8-22 14:20:00'), (614, '150/90', '2022-8-22 15:50:00'), (614, '149/89', '2022-8-22 16:05:00'),
(297, '139/79', '2022-8-22 14:05:00'), (297, '138/78', '2022-8-22 14:20:00'), (297, '140/80', '2022-8-22 15:50:00'),
(614, '150/90', '2022-8-22 12:50:00'), (614, '149/89', '2022-8-22 13:05:00'), (614, '148/88', '2022-8-22 13:20:00'),
(297, '139/79', '2022-8-22 16:05:00'), (297, '138/78', '2022-8-22 16:20:00');
Using this example data we can create a rCTE (Recursive Common Table Expression) and use that to get to your desired result set:
;WITH Base AS (
SELECT t.CSN, t.BloodPressure, t.ReadingDateTime, ROW_NUMBER() OVER (PARTITION BY t.CSN ORDER BY ReadingDateTime) AS Rn,
CASE WHEN LAG(t.ReadingDateTime,1) OVER (PARTITION BY t.CSN ORDER BY t.ReadingDateTime) IS NULL OR
LAG(t.ReadingDateTime,1) OVER (PARTITION BY t.CSN ORDER BY t.ReadingDateTime) < DATEADD(SECOND,-(59*60)+59,t.ReadingDateTime) THEN 1
ELSE 0
END AS IsNewGroup
FROM @Table t
), rCTE AS (
SELECT Base.CSN, Base.BloodPressure, Base.ReadingDateTime, Base.Rn, 1 AS GroupSeqeunce, ROW_NUMBER() OVER (PARTITION BY CSN ORDER BY Rn) AS GroupNumber
FROM Base
WHERE IsNewGroup = 1
UNION ALL
SELECT a.CSN, r.BloodPressure, r.ReadingDateTime, r.Rn, a.GroupSeqeunce + 1, a.GroupNumber
FROM rCTE a
INNER JOIN Base r
ON a.CSN = r.CSN
AND a.Rn = r.Rn - 1
AND r.IsNewGroup = 0
)
SELECT CSN, BloodPressure, ReadingDateTime, GroupNumber, GroupSeqeunce
FROM rCTE
ORDER BY CSN, GroupNumber, GroupSeqeunce
CSN |
BloodPressure |
ReadingDateTime |
GroupNumber |
GroupSeqeunce |
297 |
140/80 |
2022-08-22 12:50:00.000 |
1 |
1 |
297 |
139/79 |
2022-08-22 13:05:00.000 |
1 |
2 |
297 |
138/78 |
2022-08-22 13:20:00.000 |
1 |
3 |
297 |
137/77 |
2022-08-22 13:35:00.000 |
1 |
4 |
297 |
140/80 |
2022-08-22 13:50:00.000 |
1 |
5 |
297 |
139/79 |
2022-08-22 14:05:00.000 |
1 |
6 |
297 |
138/78 |
2022-08-22 14:20:00.000 |
1 |
7 |
297 |
140/80 |
2022-08-22 15:50:00.000 |
2 |
1 |
297 |
139/79 |
2022-08-22 16:05:00.000 |
2 |
2 |
297 |
138/78 |
2022-08-22 16:20:00.000 |
2 |
3 |
614 |
150/90 |
2022-08-22 12:50:00.000 |
1 |
1 |
614 |
149/89 |
2022-08-22 13:05:00.000 |
1 |
2 |
614 |
148/88 |
2022-08-22 13:20:00.000 |
1 |
3 |
614 |
147/87 |
2022-08-22 13:35:00.000 |
1 |
4 |
614 |
150/90 |
2022-08-22 13:50:00.000 |
1 |
5 |
614 |
149/89 |
2022-08-22 14:05:00.000 |
1 |
6 |
614 |
148/88 |
2022-08-22 14:20:00.000 |
1 |
7 |
614 |
150/90 |
2022-08-22 15:50:00.000 |
2 |
1 |
614 |
149/89 |
2022-08-22 16:05:00.000 |
2 |
2 |
614 |
148/88 |
2022-08-22 16:20:00.000 |
2 |
3 |
First we're doing some testing on the rows to find the 'Starter' rows. These are rows which are 60 minutes or more away from the previous row or do not have a previous row for the same CSN. We're using LAG
with the OVER
clause to PARTITION BY
the CSN
and ORDER BY
the ReadingDateTime
.
Once we know where to start, we can use an rCTE to iterate over the rows and form groups, and sequence them. The original 'order' of the rows doesn't matter when they're sorted by the windowed functions.
This approach iterates over each row in order to compare the reading time to the current group start time:
;WITH Base AS (
SELECT a.CSN, a.BloodPressure, a.ReadingDateTime, ROW_NUMBER() OVER (PARTITION BY a.CSN ORDER BY a.ReadingDateTime) AS Rn
FROM @Table a
), rCTE AS (
SELECT a.CSN, a.BloodPressure, a.ReadingDateTime, a.Rn, a.ReadingDateTime AS GroupStartDateTime
FROM Base a
WHERE Rn = 1
UNION ALL
SELECT a.CSN, r.BloodPressure, r.ReadingDateTime, r.Rn, CASE WHEN r.ReadingDateTime < DATEADD(SECOND,3599,a.GroupStartDateTime) THEN a.GroupStartDateTime ELSE r.ReadingDateTime END AS GroupStartDateTime
FROM rCTE a
INNER JOIN Base r
ON a.CSN = r.CSN
AND a.Rn + 1 = r.Rn
)
SELECT t.CSN, t.BloodPressure, t.ReadingDateTime,
DENSE_RANK() OVER (PARTITION BY t.CSN ORDER BY t.GroupStartDateTime) AS GroupNumber,
ROW_NUMBER() OVER (PARTITION BY t.CSN, t.GroupStartDateTime ORDER BY t.ReadingDateTime) AS GroupSequence
FROM rCTE t
ORDER BY t.CSN, t.ReadingDateTime
CSN |
BloodPressure |
ReadingDateTime |
GroupNumber |
GroupSequence |
297 |
140/80 |
2022-08-22 12:50:00.000 |
1 |
1 |
297 |
139/79 |
2022-08-22 13:05:00.000 |
1 |
2 |
297 |
138/78 |
2022-08-22 13:20:00.000 |
1 |
3 |
297 |
137/77 |
2022-08-22 13:35:00.000 |
1 |
4 |
297 |
140/80 |
2022-08-22 13:50:00.000 |
2 |
1 |
297 |
139/79 |
2022-08-22 14:05:00.000 |
2 |
2 |
297 |
138/78 |
2022-08-22 14:20:00.000 |
2 |
3 |
297 |
140/80 |
2022-08-22 15:50:00.000 |
3 |
1 |
297 |
139/79 |
2022-08-22 16:05:00.000 |
3 |
2 |
297 |
138/78 |
2022-08-22 16:20:00.000 |
3 |
3 |
614 |
150/90 |
2022-08-22 12:50:00.000 |
1 |
1 |
614 |
149/89 |
2022-08-22 13:05:00.000 |
1 |
2 |
614 |
148/88 |
2022-08-22 13:20:00.000 |
1 |
3 |
614 |
147/87 |
2022-08-22 13:35:00.000 |
1 |
4 |
614 |
150/90 |
2022-08-22 13:50:00.000 |
2 |
1 |
614 |
149/89 |
2022-08-22 14:05:00.000 |
2 |
2 |
614 |
148/88 |
2022-08-22 14:20:00.000 |
2 |
3 |
614 |
150/90 |
2022-08-22 15:50:00.000 |
3 |
1 |
614 |
149/89 |
2022-08-22 16:05:00.000 |
3 |
2 |
614 |
148/88 |
2022-08-22 16:20:00.000 |
3 |
3 |