Nie sądzę, że można to zrobić tanio za pomocą zwykłego zapytania, CTE i funkcji okna – ich definicja ramki jest statyczna, ale potrzebujesz ramki dynamicznej (w zależności od wartości kolumn).
Ogólnie rzecz biorąc, musisz ostrożnie zdefiniować dolną i górną granicę swojego okna:Następujące zapytania wyklucz bieżący wiersz i uwzględnij dolna granica.
Nadal istnieje niewielka różnica:funkcja obejmuje poprzednich elementów równorzędnych bieżącego wiersza, podczas gdy skorelowane podzapytanie ich wyklucza...
Przypadek testowy
Korzystanie z ts
zamiast słowa zastrzeżonego date
jako nazwę kolumny.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - zapytanie Romana
Używaj CTE, agreguj znaczniki czasu w tablicę, rozmieszczaj, licz...
Prawdopodobnie wydajność drastycznie spada z więcej niż garścią rzędów. Jest tu kilku zabójców wydajności. Zobacz poniżej.
ARR - liczba elementów tablicy
Wziąłem zapytanie Romana i spróbowałem go nieco usprawnić:
- Usuń drugie CTE, które nie jest konieczne.
- Przekształć 1. CTE w podzapytanie, które jest szybsze.
- Bezpośrednie
count()
zamiast ponownego agregowania w tablicę i liczenia za pomocąarray_length()
.
Ale obsługa macierzy jest kosztowna, a wydajność nadal pogarsza się z większą liczbą wierszy.
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 — skorelowane podzapytanie
możesz rozwiąż to za pomocą prostego skorelowanego podzapytania. Dużo szybciej, ale nadal...
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 — funkcja
Zapętlaj wiersze w kolejności chronologicznej za pomocą row_number()
w funkcji plpgsql i połącz to z kursorem w tym samym zapytaniu, obejmującym żądany przedział czasu. Następnie możemy po prostu odjąć numery wierszy:
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$;
Zadzwoń z domyślnym interwałem jednej godziny:
SELECT * FROM running_window_ct();
Lub z dowolną przerwą:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>graj tutaj
Stary sqlfiddle
Wzorzec
Korzystając z powyższej tabeli, przeprowadziłem szybki test porównawczy na moim starym serwerze testowym:(PostgreSQL 9.1.9 na Debianie).
-- 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
Zmieniłem pogrubienie część dla każdego biegu i zdobyła 5 najlepszych dzięki EXPLAIN ANALYZE
.
100 wierszy
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1115 ms
1000 wierszy
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms
5000 wierszy
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000 wierszy
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
Funkcja jest wyraźnym zwycięzcą. Jest najszybszy o rząd wielkości i najlepiej skaluje się.
Obsługa macierzy nie może konkurować.