Slutfö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
Ger nu det begärda resultatet och på riktigt täcker alla möjliga fall, inklusive disjunkta undergrupper och dubbletter. Ta en titt på kommentarerna till testdata i fungerande demo på data.SE .
-
CTE 1
Hitta de (unika!) tidpunkterna där en ny grupp av överlappande intervall börjar. -
CTE 2
Räkna starten av en ny grupp upp till (och inklusive) varje enskilt intervall, och bildar därmed ett unikt gruppnummer per användare. -
Final SELECT
Slå samman grupperna, ta tidig start och senaste slutet för grupper.
Jag stötte på vissa svårigheter eftersom T-SQL-fönster fungerar max()
eller sum()
acceptera inte en ORDER BY
klausul i en i ett fönster. De kan bara beräkna ett värde per partition, vilket gör det omöjligt att beräkna en löpande summa/antal per partition. Skulle fungera i PostgreSQL eller Oracle (men inte i MySQL, naturligtvis - det har varken fönsterfunktioner eller CTEs).
Den slutliga lösningen använder en extra CTE och bör vara lika snabb.