sql >> Base de Datos >  >> RDS >> PostgreSQL

Cómo obtener valores promedio para intervalos de tiempo en Postgres

Diseño de base de datos

Mientras puedas trabajar con date separados y time columnas, realmente no hay ninguna ventaja sobre un solo timestamp columna. Adaptaría:

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;

Si la fecha y la hora no son reales date y time tipos de datos, use to_timestamp() . Relacionado:

Consulta

Entonces la consulta es un poco más simple:

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 aquí

Genere una grilla de horas de inicio en la primera subconsulta grid , desde el primero hasta el último calificado fila en el marco de tiempo dado.

Únase a las filas que caen en cada partición con un LATERAL unirse e inmediatamente agregar promedios en la subconsulta avg . Debido a los agregados, siempre devuelve una fila incluso si no se encuentran entradas. Los promedios predeterminados son NULL en este caso.

El resultado incluye todos los intervalos de tiempo entre la primera y la última fila de calificación en el marco de tiempo dado. Varias otras composiciones de resultados también tendrían sentido. Como incluir todos intervalos de tiempo en el marco de tiempo dado o solo intervalos de tiempo con valores reales. De todas las posibles, tuve que elegir una interpretación.

Índice

Al menos tenga este índice de varias columnas:

CRATE INDEX foo_idx ON tbl (sn, ts);

O en (sn, ts, vin1, vin2, vin3) para permitir escaneos de solo índice, si se cumplen algunas condiciones previas y especialmente si las filas de la tabla son mucho más anchas que en la demostración.

Muy relacionado:

Basado en su tabla original

Según lo solicitado y aclarado en el comentario , y luego se actualizó nuevamente en la pregunta para incluir las columnas mac y loc . Supongo que desea promedios separados por (mac, loc) .

date y time siguen siendo columnas separadas, las columnas vin* son de tipo float y excluir intervalos de tiempo sin filas:

La consulta actualizada también mueve la función de devolución de conjuntos generate_series() al FROM lista, que es más limpia antes de 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;

Cree un índice de expresión de varias columnas para respaldar esto:

CRATE INDEX bar_idx ON tbl (sn, (date+time));

db<>fiddle aquí

Pero preferiría usar timestamp todo el tiempo.