Fuldfør omskrivning:
;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
Producerer nu det ønskede resultat og virkelig dækker alle mulige tilfælde, inklusive adskilte undergrupper og dubletter. Se kommentarerne til testdataene i fungerende demo på data.SE .
-
CTE 1
Find de (unikke!) tidspunkter, hvor en ny gruppe af overlappende intervaller starter. -
CTE 2
Tæl starten af en ny gruppe op til (og inklusive) hvert enkelt interval og danner derved et unikt gruppenummer pr. bruger. -
Endelig VALG
Flet grupperne sammen, tag tidlig start og sidste slutning for grupper.
Jeg stod over for nogle vanskeligheder, fordi T-SQL-vinduets funktioner max()
eller sum()
accepter ikke en ORDER BY
klausul i a i et vindue. De kan kun beregne én værdi pr. partition, hvilket gør det umuligt at beregne en løbende sum/antal pr. partition. Ville fungere i PostgreSQL eller Oracle (men ikke i MySQL, selvfølgelig - det har hverken vinduesfunktioner eller CTE'er).
Den endelige løsning bruger en ekstra CTE og bør være lige så hurtig.