Návrh DB
Zatímco můžete pracovat se samostatným date
a time
sloupců, skutečně neexistuje žádná výhoda oproti jedinému timestamp
sloupec. Přizpůsobil bych se:
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;
Pokud datum a čas nejsou skutečné date
a time
datové typy, použijte to_timestamp()
. Související:
- Výpočet kumulativního součtu v PostgreSQL
- Jak převést „řetězec“ na „časové razítko bez časového pásma“
Dotaz
Pak je dotaz o něco jednodušší:
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 zde
Vygenerujte mřížku časů zahájení v prvním dílčím dotazu grid
, běží od první do poslední kvalifikace řádek v daném časovém rámci.
Připojte se k řádkům, které spadají do každého oddílu, pomocí LATERAL
připojit a okamžitě agregovat průměry v poddotazu avg
. Kvůli agregátům je to vždy vrátí řádek, i když nebyly nalezeny žádné položky. Výchozí průměry jsou NULL
v tomto případě.
Výsledek zahrnuje všechny časové úseky mezi první a poslední kvalifikační řadou v daném časovém rámci. Smysl by dávaly i různé další výsledné kompozice. Jako včetně všech časové úseky v daném časovém rámci nebo pouze časové úseky se skutečnými hodnotami. Všechny možné, musel jsem vybrat jeden výklad.
Index
Mějte alespoň tento vícesloupcový index:
CRATE INDEX foo_idx ON tbl (sn, ts);
Nebo na (sn, ts, vin1, vin2, vin3)
umožnit skenování pouze na základě indexu – pokud jsou splněny některé předpoklady a zejména pokud jsou řádky tabulky mnohem širší než v ukázce.
Úzce související:
- Pomalé LEFT JOIN na CTE s časovými intervaly
- Nejlepší způsob, jak počítat záznamy podle libovolných časových intervalů v Rails+Postgres
Na základě vaší původní tabulky
Jak bylo požadováno a vysvětleno v komentáři
a později znovu aktualizován v otázce, aby zahrnoval sloupce mac
a loc
. Předpokládám, že chcete samostatné průměry pro (mac, loc)
.
date
a time
jsou stále samostatné sloupce, sloupce vin* jsou typu float
a vyloučit časové úseky bez řádků:
Aktualizovaný dotaz také přesune funkci vracející sadu generate_series()
do FROM
seznam, který je před Postgres 10 č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;
Vytvořte vícesloupcový index výrazu, který toto podporuje:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle zde
Ale mnohem raději bych použil timestamp
po celou dobu.