Dokončit přepsání:
;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
Nyní vytváří požadovaný výsledek a opravdu pokrývá všechny možné případy, včetně oddělených podskupin a duplikátů. Podívejte se na komentáře k testovacím datům v pracovní ukázka na data.SE .
-
CTE 1
Najděte (jedinečné!) body v čase, kde začíná nová skupina překrývajících se intervalů. -
CTE 2
Počítejte začátek nové skupiny až do (včetně) každého jednotlivého intervalu, čímž vytvoříte jedinečné číslo skupiny na uživatele. -
Konečný SELECT
Sloučit skupiny, vybrat dřívější začátek a konec pro skupiny.
Potýkal jsem se s určitými potížemi, protože funkce okna T-SQL max()
nebo sum()
nepřijímejte ORDER BY
klauzule v okně. Mohou vypočítat pouze jednu hodnotu na oddíl, což znemožňuje vypočítat průběžný součet / počet na oddíl. Fungovalo by to v PostgreSQL nebo Oracle (ale ne v MySQL, samozřejmě - nemá ani okenní funkce, ani CTE).
Konečné řešení využívá jeden CTE navíc a mělo by být stejně rychlé.