Jeg tror ikke, du kan gøre dette billigt med en almindelig forespørgsel, CTE'er og vinduesfunktioner - deres rammedefinition er statisk, men du har brug for en dynamisk ramme (afhængigt af kolonneværdier).
Generelt skal du omhyggeligt definere nedre og øvre grænse for dit vindue:Følgende forespørgsler ekskluderer den aktuelle række og inkluder den nederste kant.
Der er stadig en mindre forskel:Funktionen inkluderer tidligere peers i den aktuelle række, mens den korrelerede underforespørgsel udelukker dem ...
Testcase
Brug af ts
i stedet for reserveret ord date
som kolonnenavn.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - Romans forespørgsel
Brug CTE'er, samle tidsstempler i et array, unnest, tæl ...
Selvom det er korrekt, forringes ydeevnen drastisk med mere end en hånd fuld af rækker. Der er et par præstationsdræbere her. Se nedenfor.
ARR - tæl array-elementer
Jeg tog Romans forespørgsel og prøvede at strømline den lidt:
- Fjern 2. CTE, som ikke er nødvendigt.
- Omdan 1. CTE til underforespørgsel, hvilket er hurtigere.
- Direkte
count()
i stedet for at re-aggregere til en matrix og tælle medarray_length()
.
Men array-håndtering er dyrt, og ydeevnen forringes stadig dårligt med flere rækker.
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 - korreleret underforespørgsel
Du kunne løse det med en simpel korreleret underforespørgsel. Meget hurtigere, men alligevel ...
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 - Funktion
Loop over rækker i kronologisk rækkefølge med en row_number()
i plpgsql-funktionen og kombiner det med en markør over den samme forespørgsel, der strækker sig over den ønskede tidsramme. Så kan vi bare trække rækkenumre fra:
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$;
Opkald med standardinterval på en time:
SELECT * FROM running_window_ct();
Eller med et hvilket som helst interval:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>spil her
Gamle sqlfiddle
Benchmark
Med tabellen ovenfra kørte jeg et hurtigt benchmark på min gamle testserver:(PostgreSQL 9.1.9 på 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
Jeg varierede fed del for hvert løb og tog det bedste af 5 med EXPLAIN ANALYZE
.
100 rækker
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms
1000 rækker
ROM:2116.029 ms
ARR:189.679 ms
COR:65.802 ms
FNC:8.466 ms
5000 rækker
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100.000 rækker
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
Funktionen er den klare sejrherre. Den er hurtigst i en størrelsesorden og skalerer bedst.
Arrayhåndtering kan ikke konkurrere.