Пълно пренаписване:
;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
Сега произвежда искания резултат инаистина покрива всички възможни случаи, включително отделни подгрупи и дубликати. Разгледайте коментарите към тестовите данни в работна демонстрация в data.SE .
-
CTE 1
Намерете (уникалните!) точки във времето, където започва нова група от припокриващи се интервали. -
CTE 2
Преброява стартиранията на нова група до (и включително) всеки отделен интервал, като по този начин формира уникален номер на група за потребител. -
Краен ИЗБОР
Обединете групите, вземете по-ранен старт и най-късен край за групите.
Сблъсках се с известна трудност, тъй като прозоречните функции на T-SQL max()
или sum()
не приемайте ORDER BY
клауза в a в прозорец. Те могат да изчислят само една стойност на дял, което прави невъзможно изчисляването на текуща сума/брой на дял. Ще работи в PostgreSQL или Oracle (но не и в MySQL, разбира се - няма нито функции за прозорец, нито CTE).
Крайното решение използва един допълнителен CTE и трябва да бъде също толкова бързо.