Sử dụng một số chức năng cửa sổ khác nhau và hai truy vấn con, điều này sẽ hoạt động rất nhanh:
WITH events(id, event, ts) AS (
VALUES
(1, 12, '2014-03-19 08:00:00'::timestamp)
,(2, 12, '2014-03-19 08:30:00')
,(3, 13, '2014-03-19 09:00:00')
,(4, 13, '2014-03-19 09:30:00')
,(5, 12, '2014-03-19 10:00:00')
)
SELECT first_value(pre_id) OVER (PARTITION BY grp ORDER BY ts) AS pre_id
, id, ts
, first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM (
SELECT *, count(step) OVER w AS grp
FROM (
SELECT id, ts
, NULLIF(lag(event) OVER w, event) AS step
, lag(id) OVER w AS pre_id
, lead(id) OVER w AS post_id
FROM events
WINDOW w AS (ORDER BY ts)
) sub1
WINDOW w AS (ORDER BY ts)
) sub2
ORDER BY ts;
Sử dụng ts
làm tên cho cột dấu thời gian.
Giả sử ts
là duy nhất - và được lập chỉ mục (một ràng buộc duy nhất thực hiện điều đó tự động).
Trong một thử nghiệm với bảng ngoài đời thực có 50k hàng, nó chỉ cần một lần quét chỉ mục . Vì vậy, nên nhanh chóng ngay cả với các bảng lớn. Trong khi đó, truy vấn của bạn với tham gia / phân biệt đã không kết thúc sau một phút (như mong đợi).
Ngay cả phiên bản được tối ưu hóa, xử lý một liên kết chéo tại một thời điểm (liên kết bên trái hầu như không có điều kiện giới hạn cũng là một giới hạn tham gia chéo) đã không kết thúc sau một phút.
Để có hiệu suất tốt nhất với một bảng lớn, hãy điều chỉnh cài đặt bộ nhớ của bạn, đặc biệt cho work_mem
(đối với các phép toán sắp xếp lớn). Tạm thời cân nhắc đặt nó (nhiều) cao hơn cho phiên của bạn nếu bạn có thể dự phòng RAM. Đọc thêm tại đây và tại đây.
Làm thế nào?
-
Trong truy vấn con
sub1
nhìn vào sự kiện từ hàng trước đó và chỉ giữ lại sự kiện đó nếu nó đã thay đổi, do đó đánh dấu phần tử đầu tiên của một nhóm mới. Đồng thời, lấyid
của hàng trước và hàng tiếp theo (pre_id
,post_id
). -
Trong truy vấn con
sub2
,count()
chỉ đếm các giá trị không rỗng.grp
kết quả đánh dấu các đồng nghiệp trong các khối các sự kiện giống nhau liên tiếp. -
Trong
SELECT
cuối cùng , lấypre_id
đầu tiên vàpost_id
cuối cùng mỗi nhóm để mỗi hàng đi đến kết quả mong muốn.
Thực ra, điều này sẽ nhanh hơn trongSELECT
bên ngoài :last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... vì thứ tự sắp xếp của cửa sổ đồng ý với cửa sổ cho
pre_id
, vì vậy chỉ cần một loại duy nhất. Một bài kiểm tra nhanh dường như xác nhận điều đó. Tìm hiểu thêm về định nghĩa khung này.
SQL Fiddle.