Volledig herschrijven:
;WITH new_grp AS (
SELECT r1.UserId, r1.StartTime
FROM @requests r1
WHERE NOT EXISTS (
SELECT *
FROM @requests r2
WHERE r1.UserId = r2.UserId
AND r2.StartTime < r1.StartTime
AND r2.EndTime >= r1.StartTime)
GROUP BY r1.UserId, r1.StartTime -- there can be > 1
),r AS (
SELECT r.RequestId, r.UserId, r.StartTime, r.EndTime
,count(*) AS grp -- guaranteed to be 1+
FROM @requests r
JOIN new_grp n ON n.UserId = r.UserId AND n.StartTime <= r.StartTime
GROUP BY r.RequestId, r.UserId, r.StartTime, r.EndTime
)
SELECT min(RequestId) AS RequestId
,UserId
,min(StartTime) AS StartTime
,max(EndTime) AS EndTime
FROM r
GROUP BY UserId, grp
ORDER BY UserId, grp
Produceert nu het gevraagde resultaat en echt omvat alle mogelijke gevallen, inclusief gescheiden subgroepen en duplicaten. Bekijk de opmerkingen bij de testgegevens in de werkende demo op data.SE .
-
CTE 1
Zoek de (unieke!) tijdstippen waarop een nieuwe groep overlappende intervallen begint. -
CTE 2
Tel de start van een nieuwe groep tot (en inclusief) elke individuele interval, en vormt zo een uniek groepsnummer per gebruiker. -
Finale SELECT
Voeg de groepen samen, neem vroege start en laatste einde voor groepen.
Ik had wat problemen, omdat T-SQL-vensterfuncties max()
of sum()
accepteer geen ORDER BY
clausule in a in een venster. Ze kunnen slechts één waarde per partitie berekenen, waardoor het onmogelijk is om een lopende som / telling per partitie te berekenen. Zou werken in PostgreSQL of Oracle (maar niet in MySQL natuurlijk - het heeft geen vensterfuncties of CTE's).
De uiteindelijke oplossing gebruikt één extra CTE en zou net zo snel moeten zijn.