Tôi không nghĩ rằng bạn có thể làm điều này một cách rẻ tiền với một truy vấn đơn giản, CTE và các hàm cửa sổ - định nghĩa khung của chúng là tĩnh, nhưng bạn cần khung động (tùy thuộc vào giá trị cột).
Nói chung, bạn sẽ phải xác định cận dưới và giới hạn trên của cửa sổ một cách cẩn thận:Các truy vấn sau đây loại trừ hàng hiện tại và bao gồm đường viền dưới.
Vẫn có một sự khác biệt nhỏ:hàm bao gồm các hàm tương tự trước đó của hàng hiện tại, trong khi truy vấn con tương quan loại trừ chúng ...
Trường hợp thử nghiệm
Sử dụng ts
thay vì từ dành riêng date
dưới dạng tên cột.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - Truy vấn của Roman
Sử dụng CTE, tổng hợp dấu thời gian thành một mảng, không tốt nhất, đếm ...
Mặc dù đúng, hiệu suất giảm mạnh với hơn một bàn tay đầy hàng. Có một vài sát thủ hiệu suất ở đây. Xem bên dưới.
ARR - đếm phần tử mảng
Tôi lấy truy vấn của Roman và cố gắng sắp xếp hợp lý một chút:
- Xóa CTE thứ 2 không cần thiết.
- Chuyển đổi CTE đầu tiên thành truy vấn con, nhanh hơn.
-
count()
trực tiếp thay vì tổng hợp lại thành một mảng và đếm vớiarray_length()
.
Tuy nhiên, việc xử lý mảng rất tốn kém và hiệu suất vẫn giảm sút nghiêm trọng với nhiều hàng hơn.
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 - truy vấn con tương quan
Bạn có thể giải quyết nó bằng một truy vấn con tương quan đơn giản. Nhanh hơn rất nhiều, nhưng vẫn ...
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 - Chức năng
Lặp lại các hàng theo thứ tự thời gian với row_number()
trong hàm plpgsql và kết hợp con trỏ đó với con trỏ trên cùng một truy vấn, trải dài trong khung thời gian mong muốn. Sau đó, chúng ta chỉ có thể trừ số hàng:
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$;
Gọi với khoảng thời gian mặc định là một giờ:
SELECT * FROM running_window_ct();
Hoặc với bất kỳ khoảng thời gian nào:
SELECT * FROM running_window_ct('2 hour - 3 second');
db <> fiddle here
Sqlfiddle cũ
Điểm chuẩn
Với bảng từ trên, tôi đã chạy một điểm chuẩn nhanh trên máy chủ thử nghiệm cũ của mình:(PostgreSQL 9.1.9 trên 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
Tôi đã thay đổi in đậm phần cho mỗi lần chạy và chiếm phần tốt nhất trong số 5 phần với EXPLAIN ANALYZE
.
100 hàng
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC: 1,115 ms
1000 hàng
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC: 8,466 ms
5000 hàng
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC: 42 ms
100000 hàng
ROM:DNF
ARR:DNF
COR:6760 ms
FNC: 828 ms
Chức năng là người chiến thắng rõ ràng. Nó là nhanh nhất theo thứ tự độ lớn và tỷ lệ tốt nhất.
Xử lý mảng không thể cạnh tranh.