Non penso che tu possa farlo a buon mercato con una semplice query, CTE e funzioni della finestra:la loro definizione del frame è statica, ma hai bisogno di un frame dinamico (a seconda dei valori delle colonne).
In genere, dovrai definire con attenzione il limite inferiore e superiore della finestra:Le seguenti query escludi la riga corrente e includi il bordo inferiore.
C'è ancora una piccola differenza:la funzione include i peer precedenti della riga corrente, mentre la subquery correlata li esclude ...
Caso di prova
Usando ts
invece della parola riservata date
come nome della colonna.
CREATE TABLE test (
id bigint
, ts timestamp
);
ROM - Domanda di Roman
Utilizzare CTE, aggregare timestamp in un array, annullare l'annidamento, contare...
Sebbene corretti, le prestazioni deteriorano drasticamente con più di una mano piena di righe. Ci sono un paio di killer di prestazioni qui. Vedi sotto.
ARR - conta gli elementi dell'array
Ho preso la domanda di Roman e ho cercato di snellirla un po':
- Rimuovi il 2° CTE che non è necessario.
- Trasforma il 1° CTE in subquery, che è più veloce.
- Diretto
count()
invece di riaggregare in un array e contare conarray_length()
.
Ma la gestione dell'array è costosa e le prestazioni si deteriorano ancora gravemente con più righe.
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 - sottoquery correlata
Potresti risolverlo con una semplice subquery correlata. Molto più veloce, ma comunque...
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 - Funzione
Scorri le righe in ordine cronologico con un row_number()
nella funzione plpgsql e combinalo con un cursore sulla stessa query, coprendo l'intervallo di tempo desiderato. Quindi possiamo semplicemente sottrarre i numeri di riga:
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$;
Chiamata con intervallo predefinito di un'ora:
SELECT * FROM running_window_ct();
O con qualsiasi intervallo:
SELECT * FROM running_window_ct('2 hour - 3 second');
db<>gioca qui
Sqlfiddle vecchio
Parametro
Con la tabella sopra ho eseguito un rapido benchmark sul mio vecchio server di test:(PostgreSQL 9.1.9 su 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
Ho variato il grassetto parte per ogni corsa e ha preso il meglio di 5 con EXPLAIN ANALYZE
.
100 righe
ROM:27,656 ms
ARR:7,834 ms
COR:5,488 ms
FNC:1,115 ms
1000 righe
ROM:2116,029 ms
ARR:189,679 ms
COR:65,802 ms
FNC:8,466 ms
5000 righe
ROM:51347 ms !!
ARR:3167 ms
COR:333 ms
FNC:42 ms
100000 righe
ROM:DNF
ARR:DNF
COR:6760 ms
FNC:828 ms
La funzione è il chiaro vincitore. È il più veloce di un ordine di grandezza e si adatta meglio.
La gestione degli array non può competere.