Nemyslím si, že to můžete udělat levně pomocí prostého dotazu, CTE a okenních funkcí - jejich definice rámce je statická, ale potřebujete dynamický rámec (v závislosti na hodnotách sloupců).
Obecně budete muset pečlivě definovat spodní a horní hranici okna:Následující dotazy vylučují aktuální řádek a zahrnout spodní okraj.
Je zde ještě malý rozdíl:funkce zahrnuje předchozí partnery aktuálního řádku, zatímco korelovaný poddotaz je vylučuje ...
Testovací případ
Pomocí ts
místo vyhrazeného slova date
jako název sloupce.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM – Romanův dotaz
Používejte CTE, agregujte časová razítka do pole, zrušte vnoření, počítejte ...
I když je to správné, výkon se drasticky zhoršuje s více než rukou plnou řádků. Je zde pár zabijáků výkonu. Viz níže.
ARR - počítání prvků pole
Vzal jsem Romanův dotaz a pokusil se ho trochu zefektivnit:
- Odstraňte 2. CTE, což není nutné.
- Převeďte 1. CTE na poddotaz, což je rychlejší.
- Přímý
count()
místo reagregace do pole a počítání pomocíarray_length()
.
Manipulace s poli je ale drahá a výkon se stále závažně zhoršuje s více řádky.
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 – korelovaný dílčí dotaz
Mohli byste vyřešte to jednoduchým korelovaným poddotazem. Mnohem rychlejší, ale stále ...
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 – funkce
Opakujte řádky v chronologickém pořadí pomocí row_number()
ve funkci plpgsql a zkombinujte to s kurzorem přes stejný dotaz v požadovaném časovém rámci. Pak můžeme jen odečíst čísla řádků:
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$;
Volání s výchozím intervalem jedné hodiny:
SELECT * FROM running_window_ct();
Nebo s libovolným intervalem:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>zde hrajte
Starý sqlfiddle
Srovnávací
S tabulkou shora jsem spustil rychlý benchmark na mém starém testovacím serveru:(PostgreSQL 9.1.9 na Debianu).
-- 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
Změnil jsem tučné část pro každý běh a získal nejlepší z 5 pomocí EXPLAIN ANALYZE
.
100 řádků
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms
1000 řádků
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms
5 000 řádků
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100 000 řádků
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
Funkce je jasným vítězem. Je o řád nejrychlejší a má nejlepší měřítko.
Ovládání pole nemůže konkurovat.