DB-ontwerp
Terwijl je kunt werk met aparte date
en time
kolommen, is er echt geen voordeel ten opzichte van een enkele timestamp
kolom. Ik zou me aanpassen:
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;
Als datum en tijd niet de werkelijke date
zijn en time
datatypes, gebruik to_timestamp()
. Gerelateerd:
- Cumulatieve som berekenen in PostgreSQL
- Hoe "string" naar "timestamp zonder tijdzone" te converteren
Zoekopdracht
Dan is de vraag wat eenvoudiger:
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
Genereer een raster van starttijden in de eerste subquery grid
, lopend van de eerste tot de laatste kwalificatie rij in het opgegeven tijdsbestek.
Voeg toe aan rijen die in elke partitie vallen met een LATERAL
voeg gemiddelden toe en verzamel ze onmiddellijk in de subquery avg
. Vanwege de aggregaten is het altijd geeft een rij terug, zelfs als er geen items zijn gevonden. Gemiddelden zijn standaard NULL
in dit geval.
Het resultaat omvat alle tijdvakken tussen de eerste en de laatste kwalificatierij in het opgegeven tijdsbestek. Verschillende andere resultaatsamenstellingen zouden ook logisch zijn. Vind ik leuk inclusief alles tijdvakken in het opgegeven tijdsbestek of alleen tijdvakken met werkelijke waarden. Alles mogelijk, ik moest één interpretatie kiezen.
Index
Zorg in ieder geval voor deze index met meerdere kolommen:
CRATE INDEX foo_idx ON tbl (sn, ts);
Of op (sn, ts, vin1, vin2, vin3)
om alleen-index scans toe te staan - als aan bepaalde voorwaarden is voldaan en vooral als tabelrijen veel breder zijn dan in de demo.
Nauw verwant:
- Langzaam LEFT JOIN op CTE met tijdsintervallen
- Beste manier om records te tellen met willekeurige tijdsintervallen in Rails+Postgres
Gebaseerd op je oorspronkelijke tafel
Zoals gevraagd en verduidelijkt in de opmerking
, en later opnieuw bijgewerkt in de vraag om de kolommen mac
. op te nemen en loc
. Ik neem aan dat je aparte gemiddelden wilt per (mac, loc)
.
date
en time
zijn nog steeds aparte kolommen, vin* kolommen zijn van het type float
, en sluit tijdvakken zonder rijen uit:
De bijgewerkte query verplaatst ook de set-retourfunctie generate_series()
naar de FROM
lijst, die schoner is vóór 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;
Maak een expressie-index met meerdere kolommen om dit te ondersteunen:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle hier
Maar ik gebruik liever timestamp
al die tijd.