Saya tidak berpikir Anda dapat melakukan ini dengan murah dengan kueri biasa, CTE, dan fungsi jendela - definisi bingkainya statis, tetapi Anda memerlukan bingkai dinamis (tergantung pada nilai kolom).
Umumnya, Anda harus menentukan batas bawah dan atas jendela dengan hati-hati:Kueri berikut kecualikan baris saat ini dan sertakan batas bawah.
Masih ada sedikit perbedaan:fungsi menyertakan rekan sebelumnya dari baris saat ini, sedangkan subkueri terkait mengecualikannya ...
Kasus uji
Menggunakan ts
alih-alih kata date
yang dicadangkan sebagai nama kolom.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - permintaan Roman
Gunakan CTE, gabungkan stempel waktu ke dalam larik, hapus sarang, hitung ...
Meskipun benar, kinerja menurun secara drastis dengan lebih dari satu tangan penuh baris. Ada beberapa pembunuh kinerja di sini. Lihat di bawah.
ARR - menghitung elemen larik
Saya mengambil kueri Roman dan mencoba menyederhanakannya sedikit:
- Hapus CTE ke-2 yang tidak perlu.
- Ubah CTE ke-1 menjadi subkueri, yang lebih cepat.
- Langsung
count()
alih-alih menggabungkan kembali ke dalam array dan menghitung denganarray_length()
.
Namun penanganan array itu mahal, dan kinerjanya masih memburuk dengan lebih banyak baris.
SELECT id, ts
, (SELECT count(*)::int - 1
FROM unnest(dates) x
WHERE x >= sub.ts - interval '1h') AS ct
FROM (
SELECT id, ts
, array_agg(ts) OVER(ORDER BY ts) AS dates
FROM test
) sub;
COR - subkueri terkait
Anda bisa menyelesaikannya dengan subquery berkorelasi sederhana. Jauh lebih cepat, tapi tetap saja ...
SELECT id, ts
, (SELECT count(*)
FROM test t1
WHERE t1.ts >= t.ts - interval '1h'
AND t1.ts < t.ts) AS ct
FROM test t
ORDER BY ts;
FNC - Fungsi
Ulangi baris dalam urutan kronologis dengan row_number()
di fungsi plpgsql dan gabungkan itu dengan kursor atas kueri yang sama, mencakup kerangka waktu yang diinginkan. Kemudian kita cukup mengurangi angka baris:
CREATE OR REPLACE FUNCTION running_window_ct(_intv interval = '1 hour')
RETURNS TABLE (id bigint, ts timestamp, ct int)
LANGUAGE plpgsql AS
$func$
DECLARE
cur CURSOR FOR
SELECT t.ts + _intv AS ts1, row_number() OVER (ORDER BY t.ts) AS rn
FROM test t ORDER BY t.ts;
rec record;
rn int;
BEGIN
OPEN cur;
FETCH cur INTO rec;
ct := -1; -- init
FOR id, ts, rn IN
SELECT t.id, t.ts, row_number() OVER (ORDER BY t.ts)
FROM test t ORDER BY t.ts
LOOP
IF rec.ts1 >= ts THEN
ct := ct + 1;
ELSE
LOOP
FETCH cur INTO rec;
EXIT WHEN rec.ts1 >= ts;
END LOOP;
ct := rn - rec.rn;
END IF;
RETURN NEXT;
END LOOP;
END
$func$;
Panggilan dengan interval default satu jam:
SELECT * FROM running_window_ct();
Atau dengan interval apa pun:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>main biola di sini
sqlfiddle lama
Tolok ukur
Dengan tabel di atas, saya menjalankan benchmark cepat di server pengujian lama saya:(PostgreSQL 9.1.9 di Debian).
-- TRUNCATE test;
INSERT INTO test
SELECT g, '2013-08-08'::timestamp
+ g * interval '5 min'
+ random() * 300 * interval '1 min' -- halfway realistic values
FROM generate_series(1, 10000) g;
CREATE INDEX test_ts_idx ON test (ts);
ANALYZE test; -- temp table needs manual analyze
Saya memvariasikan tebal bagian untuk setiap lari dan ambil yang terbaik dari 5 dengan EXPLAIN ANALYZE
.
100 baris
ROM:27.656 ms
ARR:7.834 ms
COR:5.488 ms
FNC:1.115 ms
1000 baris
ROM:2116.029 ms
ARR:189.679 ms
COR:65.802 ms
FNC:8.466 ms
5000 baris
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000 baris
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 md
Fungsinya adalah pemenang yang jelas. Ini tercepat menurut urutan besarnya dan skala terbaik.
Penanganan array tidak dapat bersaing.