Дизайн на DB
Докато можете работа с отделна дата
и време
колони, наистина няма предимство пред единичен timestampкод>
колона. Бих се адаптирал:
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;
Ако датата и часът не са действителната дата
и време
типове данни, използвайте to_timestamp()
. Свързани:
- Изчисляване на кумулативна сума в PostgreSQL
- Как да конвертирате "низ" в "клеймо за време без часова зона"
Заявка
Тогава заявката е малко по-проста:
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 тук
Генерирайте мрежа от начални времена в първата подзаявка grid
, протичащ от първата до последната квалификация ред в дадения период от време.
Присъединете се към редове, които попадат във всеки дял с LATERAL
присъединете и незабавно обобщете средните стойности в подзаявката ср.
. Благодарение на агрегатите, това винаги връща ред, дори ако не са намерени записи. Средните стойности по подразбиране са NULL
в този случай.
Резултатът включва всички времеви интервали между първия и последния квалифициращ ред в дадената времева рамка. Различни други резултатни композиции също биха имали смисъл. Като включване на всички времеви интервали в дадената времева рамка или само времеви интервали с действителни стойности. Всички възможни, трябваше да избера една интерпретация.
Индекс
Имайте поне този многоколонен индекс:
CRATE INDEX foo_idx ON tbl (sn, ts);
Или на (sn, ts, vin1, vin2, vin3)
за да позволи сканиране само на индекс - ако са изпълнени някои предварителни условия и особено ако редовете на таблицата са много по-широки, отколкото в демонстрацията.
Тясно свързано:
- Бавно LEFT JOIN на CTE с интервали от време
- Най-добрият начин за преброяване на записи по произволни интервали от време в Rails+Postgres
Въз основа на вашата оригинална таблица
Както е поискано и изяснено в коментара
и по-късно актуализиран отново във въпроса, за да включи колоните mac
и loc
. Предполагам, че искате отделни средни стойности за (mac, loc)
.
дата
и време
все още са отделни колони, vin* колоните са от тип float
и изключете времеви интервали без редове:
Актуализираната заявка също премества функцията за връщане на набор generate_series()
към ОТ
списък, който е по-чист преди 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;
Създайте индекс на израз с няколко колони, който да поддържа това:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle тук
Но бих предпочел да използвам timestamp
през цялото време.