PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como obter valores médios para intervalos de tempo no Postgres

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:

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:

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.