Reescritura 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
Ahora produce el resultado solicitado y realmente cubre todos los casos posibles, incluidos subgrupos separados y duplicados. Eche un vistazo a los comentarios de los datos de prueba en demostración en funcionamiento en data.SE .
-
CTE 1
Encuentre los puntos (¡únicos!) en el tiempo donde comienza un nuevo grupo de intervalos superpuestos. -
CTE 2
Cuente los inicios del nuevo grupo hasta (e incluyendo) cada intervalo individual, formando así un número de grupo único por usuario. -
SELECCIÓN final
Fusionar los grupos, tomar el inicio más temprano y el final más tardío para los grupos.
Enfrenté algunas dificultades, porque la ventana de T-SQL funciona max()
o sum()
no acepte un ORDER BY
cláusula en una en una ventana. Solo pueden calcular un valor por partición, lo que hace que sea imposible calcular una suma/recuento continuo por partición. Funcionaría en PostgreSQL u Oracle (pero no en MySQL, por supuesto, no tiene funciones de ventana ni CTE).
La solución final utiliza un CTE adicional y debería ser igual de rápida.