Projekt bazy danych
Póki możesz praca z oddzielną datą
i czas
kolumn, tak naprawdę nie ma przewagi nad pojedynczym znacznik czasu
kolumna. Dostosowałbym:
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;
Jeśli data i godzina nie są aktualne data
i czas
typy danych, użyj to_timestamp()
. Powiązane:
- Obliczanie skumulowanej sumy w PostgreSQL
- Jak przekonwertować „ciąg” na „znacznik czasu bez strefy czasowej”
Zapytanie
Wtedy zapytanie jest nieco prostsze:
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 tutaj
Wygeneruj siatkę czasów rozpoczęcia w pierwszym podzapytaniu siatka
, od pierwszego do ostatniego kwalifikującego się wiersz w podanym przedziale czasowym.
Połącz z wierszami znajdującymi się w każdej partycji za pomocą LATERAL
dołącz i natychmiast agreguj średnie w podzapytaniu średnia
. Ze względu na agregaty zawsze zwraca wiersz, nawet jeśli nie znaleziono żadnych wpisów. Średnie domyślnie to NULL
w tym przypadku.
Wynik obejmuje wszystkie przedziały czasowe między pierwszym a ostatnim wierszem kwalifikującym w danym przedziale czasowym. Różne inne kompozycje wyników również miałyby sens. Jak w tym wszystkie przedziały czasowe w danym przedziale czasowym lub po prostu przedziały czasowe z rzeczywistymi wartościami. Wszystko możliwe, musiałem wybrać jedną interpretację.
Indeks
Miej przynajmniej ten wielokolumnowy indeks:
CRATE INDEX foo_idx ON tbl (sn, ts);
Lub na (sn, ts, vin1, vin2, vin3)
aby umożliwić skanowanie tylko indeksu - jeśli spełnione są pewne warunki wstępne, a zwłaszcza jeśli wiersze tabeli są znacznie szersze niż w wersji demonstracyjnej.
Ściśle powiązane:
- Zwolnij LEWE JOIN na CTE w odstępach czasu
- Najlepszy sposób liczenia rekordów w dowolnych odstępach czasu w Rails+Postgres
Na podstawie Twojej oryginalnej tabeli
Zgodnie z prośbą i wyjaśnieniem w komentarzu
, a później ponownie zaktualizowane w pytaniu, aby uwzględnić kolumny mac
i loc
. Zakładam, że potrzebujesz oddzielnych średnich na (mac, loc)
.
data
i czas
są nadal oddzielnymi kolumnami, kolumny vin* są typu float
i wyklucz przedziały czasowe bez wierszy:
Zaktualizowane zapytanie przenosi również funkcję zwracającą zestaw generate_series()
do OD
lista, która jest czystsza przed Postgresem 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;
Utwórz indeks wyrażeń wielokolumnowych do obsługi tego:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle tutaj
Ale wolałbym używać sygnatury czasowej
przez cały czas.