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:
- Calcolo della somma cumulativa in PostgreSQL
- Come convertire "string" in "timestamp senza fuso orario"
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:
- Slow LEFT JOIN su CTE con intervalli di tempo
- Il modo migliore per contare i record in base a intervalli di tempo arbitrari in Rails+Postgres
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.