Całkowite przepisywanie:
;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
Teraz generuje żądany wynik i naprawdę obejmuje wszystkie możliwe przypadki, w tym rozłączne podgrupy i duplikaty.Zapoznaj się z komentarzami do danych testowych w działająca prezentacja na data.SE .
-
CTE 1
Znajdź (unikalne!) punkty w czasie, w których zaczyna się nowa grupa nakładających się przedziałów. -
CTE 2
Policz początki nowej grupy do (włącznie) każdego indywidualnego interwału, tworząc w ten sposób unikalny numer grupy na użytkownika. -
Końcowy WYBIERZ
Połącz grupy, weź wczesny początek i najpóźniejszy koniec dla grup.
Napotkałem pewną trudność, ponieważ funkcje okna T-SQL max()
lub sum()
nie akceptuj ORDER BY
klauzula w oknie. Mogą obliczyć tylko jedną wartość na partycję, co uniemożliwia obliczenie bieżącej sumy / liczby na partycję. Działałby w PostgreSQL lub Oracle (ale oczywiście nie w MySQL - nie ma on ani funkcji okna, ani CTE).
Ostateczne rozwiązanie wykorzystuje jeden dodatkowy CTE i powinno być równie szybkie.