Design de banco de dados
Enquanto você pode trabalhar com
data
separada e hora
colunas, não há realmente nenhuma vantagem sobre um único timestamp
coluna. Eu adaptaria: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;
Se a data e a hora não forem
data
reais e hora
tipos de dados, use to_timestamp()
. Relacionado:- Calculando a soma cumulativa no PostgreSQL
- Como converter "string" para "timestamp sem fuso horário"
Consulta
Então a consulta é um pouco mais simples:
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 aqui
Gere uma grade de horários de início na primeira subconsulta
grade
, do primeiro ao último qualificação linha no período de tempo determinado. Junte-se às linhas que caem em cada partição com um
LATERAL
junte e agregue imediatamente as médias na subconsulta avg
. Devido aos agregados, sempre retorna uma linha mesmo se nenhuma entrada for encontrada. Médias padrão para NULL
nesse caso. O resultado inclui todos os intervalos de tempo entre a primeira e a última linha de qualificação no período de tempo determinado. Várias outras composições de resultados também fariam sentido. Como incluir todos intervalos de tempo no período de tempo determinado ou apenas intervalos de tempo com valores reais. Tudo possível, eu tive que escolher uma interpretação.
Índice
Tenha pelo menos este índice de várias colunas:
CRATE INDEX foo_idx ON tbl (sn, ts);
Ou em
(sn, ts, vin1, vin2, vin3)
para permitir varreduras somente de índice - se algumas pré-condições forem atendidas e especialmente se as linhas da tabela forem muito mais largas do que na demonstração. Intimamente relacionado:
- LEFT JOIN lento no CTE com intervalos de tempo
- Melhor maneira de contar registros por intervalos de tempo arbitrários em Rails+Postgres
Com base em sua tabela original
Conforme solicitado e esclarecido no comentário , e posteriormente atualizado novamente na pergunta para incluir as colunas
mac
e loc
. Suponho que você queira médias separadas por (mac, loc)
. data
e hora
ainda são colunas separadas, as colunas vin* são do tipo float
, e exclua intervalos de tempo sem linhas:A consulta atualizada também move a função de retorno de conjunto
generate_series()
para o DE
list, que é mais limpo antes do 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;
Crie um índice de expressão de várias colunas para dar suporte a isso:
CRATE INDEX bar_idx ON tbl (sn, (date+time));
db<>fiddle aqui
Mas eu prefiro usar
timestamp
durante todo esse tempo.