Ik denk niet dat je dit goedkoop kunt doen met een simpele query, CTE's en vensterfuncties - hun framedefinitie is statisch, maar je hebt een dynamisch frame nodig (afhankelijk van kolomwaarden).
Over het algemeen moet u de onder- en bovengrens van uw venster zorgvuldig definiëren:De volgende zoekopdrachten uitsluiten de huidige rij en opnemen de onderste rand.
Er is nog een klein verschil:de functie omvat eerdere peers van de huidige rij, terwijl de gecorreleerde subquery ze uitsluit ...
Testcase
Met behulp van ts
in plaats van gereserveerd woord date
als kolomnaam.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - Roman's vraag
Gebruik CTE's, verzamel tijdstempels in een array, verwijder, tel ...
Hoewel correct, prestaties verslechteren drastisch met meer dan een hand vol rijen. Er zijn hier een paar prestatiemoordenaars. Zie hieronder.
ARR - tel array-elementen
Ik nam de vraag van Roman en probeerde het een beetje te stroomlijnen:
- Verwijder de 2e CTE die niet nodig is.
- Transformeer de eerste CTE in een subquery, wat sneller is.
- Directe
count()
in plaats van opnieuw te aggregeren in een array en te tellen metarray_length()
.
Maar het hanteren van arrays is duur en de prestaties verslechteren nog steeds erg met meer rijen.
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 - gecorreleerde subquery
Je zou los het op met een eenvoudige gecorreleerde subquery. Een stuk sneller, maar toch ...
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 - Functie
Loop over rijen in chronologische volgorde met een row_number()
in plpgsql-functie en combineer dat met een cursor over dezelfde query, verspreid over het gewenste tijdsbestek. Dan kunnen we rijnummers gewoon aftrekken:
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$;
Bellen met standaardinterval van één uur:
SELECT * FROM running_window_ct();
Of met een interval:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>viool hier
Oude sqlfiddle
Benchmark
Met de bovenstaande tabel heb ik een snelle benchmark uitgevoerd op mijn oude testserver:(PostgreSQL 9.1.9 op 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
Ik heb de vet . gevarieerd deel voor elke run en haalde het beste van 5 met EXPLAIN ANALYZE
.
100 rijen
ROM:27.656 ms
ARR:7.834 ms
COR:5.488 ms
FNC:1.115 ms
1000 rijen
ROM:2116.029 ms
ARR:189.679 ms
COR:65.802 ms
FNC:8.466 ms
5000 rijen
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000 rijen
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
De functie is de duidelijke overwinnaar. Het is een orde van grootte het snelst en schaalt het best.
Arrayverwerking kan niet concurreren.