Menggunakan beberapa fungsi jendela yang berbeda dan dua subkueri, ini akan bekerja dengan cepat:
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;
Menggunakan ts
sebagai nama untuk kolom stempel waktu.
Dengan asumsi ts
menjadi unik - dan diindeks (batasan unik melakukannya secara otomatis).
Dalam pengujian dengan tabel kehidupan nyata dengan 50 ribu baris, hanya diperlukan pemindaian indeks tunggal . Jadi, harus cukup cepat bahkan dengan meja besar. Sebagai perbandingan, kueri Anda dengan bergabung / berbeda tidak selesai setelah satu menit (seperti yang diharapkan).
Bahkan versi yang dioptimalkan, menangani satu gabungan silang pada satu waktu (penggabungan kiri dengan kondisi yang hampir tidak membatasi secara efektif terbatas cross join) tidak selesai setelah satu menit.
Untuk performa terbaik dengan meja besar, sesuaikan setelan memori Anda, khususnya untuk work_mem
(untuk operasi sortir besar). Pertimbangkan untuk mengaturnya (jauh) lebih tinggi untuk sesi Anda sementara jika Anda dapat menghemat RAM. Baca selengkapnya di sini dan di sini.
Bagaimana?
-
Dalam subkueri
sub1
lihat acara dari baris sebelumnya dan simpan hanya jika itu telah berubah, dengan demikian menandai elemen pertama dari grup baru. Pada saat yang sama, dapatkanid
dari baris sebelumnya dan berikutnya (pre_id
,post_id
). -
Dalam subkueri
sub2
,count()
hanya menghitung nilai bukan nol.grp
. yang dihasilkan menandai rekan-rekan di blok peristiwa yang sama berturut-turut. -
Di bagian terakhir
SELECT
, ambilpre_id
pertama danpost_id
terakhir per grup untuk setiap baris untuk mencapai hasil yang diinginkan.
Sebenarnya, ini harus lebih cepat diSELECT
luar :last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... karena urutan jendela sesuai dengan jendela untuk
pre_id
, jadi hanya satu jenis yang diperlukan. Tes cepat tampaknya mengkonfirmasinya. Selengkapnya tentang definisi bingkai ini.
SQL Fiddle.