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:
- Calcular la suma acumulativa en PostgreSQL
- Cómo convertir "cadena" a "marca de tiempo sin zona horaria"
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:
- ÚNETE A LA IZQUIERDA lento en CTE con intervalos de tiempo
- La mejor manera de contar registros por intervalos de tiempo arbitrarios en Rails+Postgres
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.