PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

So erhalten Sie Durchschnittswerte für Zeitintervalle in Postgres

DB-Design

Solange Sie können mit separatem date arbeiten und time Spalten, gibt es wirklich keinen Vorteil gegenüber einem einzelnen timestamp Säule. Ich würde anpassen:

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;

Wenn Datum und Uhrzeit nicht das tatsächliche date sind und time Datentypen verwenden Sie to_timestamp() . Verwandte:

Abfrage

Dann ist die Abfrage etwas einfacher:

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<>fiddle hier

Generieren Sie ein Raster von Startzeiten in der ersten Unterabfrage grid , vom ersten bis zum letzten Qualifying Zeile im angegebenen Zeitrahmen.

Verbinden Sie sich mit Zeilen, die in jede Partition fallen, mit einem LATERAL Mittelwerte in der Unterabfrage avg verbinden und sofort aggregieren . Aufgrund der Aggregate ist es immer gibt eine Zeile zurück, auch wenn keine Einträge gefunden werden. Durchschnittswerte sind standardmäßig NULL in diesem Fall.

Das Ergebnis enthält alle Zeitfenster zwischen der ersten und letzten Qualifikationsreihe im angegebenen Zeitrahmen. Auch verschiedene andere Ergebniszusammensetzungen wären sinnvoll. Zum Beispiel alle einschließen Zeitfenster im angegebenen Zeitrahmen oder nur Zeitfenster mit tatsächlichen Werten. Nach Möglichkeit musste ich mich für eine Interpretation entscheiden.

Index

Haben Sie mindestens diesen mehrspaltigen Index:

CRATE INDEX foo_idx ON tbl (sn, ts);

Oder auf (sn, ts, vin1, vin2, vin3) Nur-Index-Scans zuzulassen - wenn einige Voraussetzungen erfüllt sind und insbesondere wenn Tabellenzeilen viel breiter sind als in der Demo.

Eng verwandt:

Basierend auf Ihrer ursprünglichen Tabelle

Wie gewünscht und im Kommentar klargestellt , und später erneut in der Frage aktualisiert, um die Spalten mac aufzunehmen und loc . Ich nehme an, Sie möchten separate Durchschnittswerte pro (mac, loc) .

date und time sind immer noch separate Spalten, vin*-Spalten sind vom Typ float , und schließen Sie Zeitfenster ohne Zeilen aus:

Die aktualisierte Abfrage verschiebt auch die set-returning-Funktion generate_series() zum FROM Liste, die vor Postgres 10 sauberer ist:

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;

Erstellen Sie einen mehrspaltigen Ausdrucksindex, um dies zu unterstützen:

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

db<>fiddle hier

Aber ich würde viel lieber timestamp verwenden die ganze Zeit.