Не мисля, че можете да направите това евтино с обикновена заявка, CTE и функции на прозореца - тяхната дефиниция на рамката е статична, но имате нужда от динамична рамка (в зависимост от стойностите на колоните).
По принцип ще трябва внимателно да дефинирате долната и горната граница на прозореца си:Следните заявки изключват текущия ред и включи долната граница.
Все още има малка разлика:функцията включва предишни партньори на текущия ред, докато свързаната подзаявка ги изключва ...
Тестов случай
Използване на ts
вместо запазена дума date
като име на колона.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - заявка на Роман
Използвайте CTE, агрегирайте времеви печати в масив, unnest, count ...
Докато е правилно, производителността се влошава драстично с повече от ръка, пълна с редове. Тук има няколко убийци на представяне. Вижте по-долу.
ARR - брой елементи на масива
Взех заявката на Роман и се опитах да я опростя малко:
- Отстранете 2-ри CTE, който не е необходим.
- Преобразувайте 1-ви CTE в подзаявка, която е по-бърза.
- Директно
count()
вместо повторно агрегиране в масив и броене сarray_length()
.
Но обработката на масиви е скъпа и производителността все още се влошава силно с повече редове.
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 – корелирана подзаявка
Вие можете решете го с проста корелирана подзаявка. Много по-бързо, но все пак...
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 – Функция
Превъртете редовете в хронологичен ред с row_number()
във функция plpgsql и комбинирайте това с курсор върху една и съща заявка, обхващаща желания период от време. Тогава можем просто да извадим номера на редове:
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$;
Обаждане с интервал по подразбиране от един час:
SELECT * FROM running_window_ct();
Или с произволен интервал:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>цигулка тук
Стар sqlfiddle
Сравнение
С таблицата отгоре направих бърз бенчмарк на моя стар тестов сървър:(PostgreSQL 9.1.9 на 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
Разнообразих судебелитете част за всяко изпълнение и взе най-доброто от 5 с EXPLAIN ANALYZE
.
100 реда
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms
1000 реда
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms
5000 реда
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100 000 реда
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
Функцията е ясният победител. Той е най-бърз с порядък на величина и се мащабира най-добре.
Обработката на масив не може да се конкурира.