PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come ottenere valori medi per intervalli di tempo in Postgres

Progettazione DB

Mentre tu puoi lavorare con date separata e time colonne, non c'è davvero alcun vantaggio rispetto a un singolo timestamp colonna. Mi adatterei:

ALTER TABLE tbl ADD column ts timestamp;
UPDATE tbl SET ts = date + time;  -- assuming actual date and time types
ALTER TABLE tbl DROP column date, DROP column time;

Se la data e l'ora non sono date effettive e time tipi di dati, usa to_timestamp() . Correlati:

Interrogazione

Quindi la query è un po' più semplice:

SELECT *
FROM  (
   SELECT sn, generate_series(min(ts), max(ts), interval '5 min') AS ts
   FROM   tbl
   WHERE  sn = '4as11111111'
   AND    ts >= '2018-01-01'
   AND    ts <  '2018-01-02'
   GROUP  BY 1
   ) grid
CROSS  JOIN LATERAL (
   SELECT round(avg(vin1), 2) AS vin1_av
        , round(avg(vin2), 2) AS vin2_av
        , round(avg(vin3), 2) AS vin3_av
   FROM   tbl
   WHERE  sn =  grid.sn
   AND    ts >= grid.ts
   AND    ts <  grid.ts + interval '5 min'
   ) avg;

db<>violino qui

Genera una griglia di orari di inizio nella prima sottoquery grid , dalla prima all'ultima qualifica riga nell'intervallo di tempo specificato.

Unisciti alle righe che rientrano in ciascuna partizione con un LATERAL unisci e aggrega immediatamente le medie nella sottoquery avg . A causa degli aggregati, sempre restituisce una riga anche se non vengono trovate voci. Il valore medio predefinito è NULL in questo caso.

Il risultato include tutte le fasce orarie comprese tra la prima e l'ultima fila di qualificazione nell'intervallo di tempo indicato. Avrebbero senso anche varie altre composizioni di risultati. Come includere tutti fasce orarie nell'intervallo di tempo specificato o solo fasce orarie con valori effettivi. Per quanto possibile, ho dovuto scegliere un'interpretazione.

Indice

Almeno avere questo indice multicolonna:

CRATE INDEX foo_idx ON tbl (sn, ts);

Oppure su (sn, ts, vin1, vin2, vin3) per consentire scansioni solo indice, se vengono soddisfatte alcune condizioni preliminari e soprattutto se le righe della tabella sono molto più larghe rispetto alla demo.

Strettamente correlato:

Basato sulla tua tabella originale

Come richiesto e chiarito nel commento , e successivamente aggiornato di nuovo nella domanda per includere le colonne mac e loc . Presumo che tu voglia medie separate per (mac, loc) .

date e time sono ancora colonne separate, le colonne vin* sono di tipo float ed escludere le fasce orarie senza righe:

La query aggiornata sposta anche la funzione di ritorno degli insiemi generate_series() al FROM list, che è più pulito prima di Postgres 10:

SELECT t.mac, sn.sn, t.loc, ts.ts::time AS time, ts.ts::date AS date
     , t.vin1_av, t.vin2_av, t.vin3_av
FROM  (SELECT text '4as11111111') sn(sn)  -- provide sn here once
CROSS  JOIN LATERAL (
   SELECT min(date+time) AS min_ts, max(date+time) AS max_ts
   FROM   tbl
   WHERE  sn = sn.sn
   AND    date+time >= '2018-01-01 0:0'   -- provide time frame here
   AND    date+time <  '2018-01-02 0:0'
   ) grid
CROSS  JOIN LATERAL generate_series(min_ts, max_ts, interval '5 min') ts(ts)
CROSS  JOIN LATERAL (
   SELECT mac, loc
        , round(avg(vin1)::numeric, 2) AS vin1_av  -- cast to numeric for round()
        , round(avg(vin2)::numeric, 2) AS vin2_av  -- but rounding is optional
        , round(avg(vin3)::numeric, 2) AS vin3_av
   FROM   tbl
   WHERE  sn = sn.sn
   AND    date+time >= ts.ts
   AND    date+time <  ts.ts + interval '5 min'
   GROUP  BY mac, loc
   HAVING count(*) > 0  -- exclude empty slots
   ) t;

Crea un indice di espressione multicolonna per supportare questo:

CRATE INDEX bar_idx ON tbl (sn, (date+time));

db<>violino qui

Ma preferirei di gran lunga usare timestamp sempre.