Riscrittura completa:
;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
Ora produce il risultato richiesto e davvero copre tutti i casi possibili, inclusi sottogruppi e duplicati disgiunti. Dai un'occhiata ai commenti ai dati del test in dimostrazione di lavoro su data.SE .
-
CTE 1
Trova i punti (unici!) nel tempo in cui inizia un nuovo gruppo di intervalli sovrapposti. -
CTE 2
Conta gli inizi di un nuovo gruppo fino a (incluso) ogni singolo intervallo, formando così un numero di gruppo univoco per utente. -
SELEZIONA FINALE
Unisci i gruppi, prendi l'inizio anticipato e l'ultima fine per i gruppi.
Ho incontrato alcune difficoltà, perché le funzioni della finestra T-SQL max()
o sum()
non accettare un ORDER BY
clausola in a in una finestra. Possono calcolare solo un valore per partizione, il che rende impossibile calcolare una somma/conteggio corrente per partizione. Funzionerebbe in PostgreSQL o Oracle (ma non in MySQL, ovviamente - non ha né funzioni di finestra né CTE).
La soluzione finale utilizza un CTE aggiuntivo e dovrebbe essere altrettanto veloce.