Réécriture complète :
;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
Produit maintenant le résultat demandé et vraiment couvre tous les cas possibles, y compris les sous-groupes disjoints et les doublons. Consultez les commentaires sur les données de test dans le démo de travail sur data.SE .
-
CTE 1
Trouvez les points (uniques !) dans le temps où commence un nouveau groupe d'intervalles qui se chevauchent. -
CTE 2
Compte les démarrages d'un nouveau groupe jusqu'à (et y compris) chaque intervalle individuel, formant ainsi un numéro de groupe unique par utilisateur. -
SELECT final
Fusionnez les groupes, prenez le début au plus tôt et la fin au plus tard pour les groupes.
J'ai rencontré quelques difficultés, car les fonctions de fenêtre T-SQL max()
ou sum()
ne pas accepter un ORDER BY
clause dans a dans une fenêtre. Ils ne peuvent calculer qu'une seule valeur par partition, ce qui rend impossible le calcul d'une somme/compte courant par partition. Fonctionnerait dans PostgreSQL ou Oracle (mais pas dans MySQL, bien sûr - il n'a ni fonctions de fenêtre ni CTE).
La solution finale utilise un CTE supplémentaire et devrait être tout aussi rapide.