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:
- Kumulative Summe in PostgreSQL berechnen
- So konvertieren Sie "String" in "Zeitstempel ohne Zeitzone"
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:
- LEFT JOIN auf CTE mit Zeitintervallen verlangsamen
- Beste Methode zum Zählen von Datensätzen nach beliebigen Zeitintervallen in Rails+Postgres
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.