Reescrita 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
Agora produz o resultado solicitado e realmente abrange todos os casos possíveis, incluindo subgrupos disjuntos e duplicatas. Dê uma olhada nos comentários aos dados de teste no demonstração de trabalho em data.SE .
-
CTE 1
Encontre os pontos (únicos!) no tempo em que um novo grupo de intervalos sobrepostos começa.
-
CTE 2
Conta o início de um novo grupo até (e incluindo) cada intervalo individual, formando assim um número de grupo único por usuário.
-
Final SELECT
Mesclar os grupos, começar mais cedo e terminar mais tarde para os grupos.
Eu enfrentei alguma dificuldade, porque a janela T-SQL funciona
max()
ou sum()
não aceite um ORDER BY
cláusula em a em uma janela. Eles só podem calcular um valor por partição, o que torna impossível calcular uma soma/contagem em execução por partição. Funcionaria no PostgreSQL ou Oracle (mas não no MySQL, é claro - não tem funções de janela nem CTEs). A solução final usa um CTE extra e deve ser tão rápida quanto.