Viết lại hoàn chỉnh:
;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
Bây giờ tạo ra kết quả được yêu cầu và thực sự bao gồm tất cả các trường hợp có thể xảy ra, bao gồm các nhóm phụ không còn tồn tại và các nhóm trùng lặp. Hãy xem nhận xét về dữ liệu thử nghiệm trong bản trình diễn làm việc tại data.SE .
-
CTE 1
Tìm các điểm (duy nhất!) Trong thời gian bắt đầu một nhóm các khoảng trùng lặp mới. -
CTE 2
Đếm số lần bắt đầu nhóm mới lên đến (và bao gồm) mọi khoảng thời gian riêng lẻ, do đó tạo thành một số nhóm duy nhất cho mỗi người dùng. -
CHỌN CHỌN cuối cùng
Hợp nhất các nhóm, bắt đầu tai nghe và kết thúc mới nhất cho các nhóm.
Tôi gặp một số khó khăn vì cửa sổ T-SQL hàm max()
hoặc sum()
không chấp nhận ORDER BY
mệnh đề trong một trong một cửa sổ. Họ chỉ có thể tính một giá trị cho mỗi phân vùng, điều này khiến cho không thể tính tổng / số đang chạy trên mỗi phân vùng. Sẽ hoạt động trong PostgreSQL hoặc Oracle (tất nhiên không phải trong MySQL - nó không có chức năng cửa sổ cũng như CTE).
Giải pháp cuối cùng sử dụng thêm một CTE và sẽ nhanh như nhau.