Afortunadamente, estás usando PostgreSQL. La función de ventana generate_series()
es tu amigo.
Caso de prueba
Dada la siguiente tabla de prueba (que usted debería haber proporcionado):
CREATE TABLE event(event_id serial, ts timestamp);
INSERT INTO event (ts)
SELECT generate_series(timestamp '2018-05-01'
, timestamp '2018-05-08'
, interval '7 min') + random() * interval '7 min';
Un evento por cada 7 minutos (más 0 a 7 minutos, al azar).
Solución básica
Esta consulta cuenta eventos para cualquier intervalo de tiempo arbitrario. 17 minutos en el ejemplo:
WITH grid AS (
SELECT start_time
, lead(start_time, 1, 'infinity') OVER (ORDER BY start_time) AS end_time
FROM (
SELECT generate_series(min(ts), max(ts), interval '17 min') AS start_time
FROM event
) sub
)
SELECT start_time, count(e.ts) AS events
FROM grid g
LEFT JOIN event e ON e.ts >= g.start_time
AND e.ts < g.end_time
GROUP BY start_time
ORDER BY start_time;
-
La consulta recupera
ts
mínimo y máximo de la tabla base para cubrir el rango de tiempo completo. En su lugar, puede utilizar un intervalo de tiempo arbitrario. -
Proporcione cualquiera intervalo de tiempo según sea necesario.
-
Produce una fila para cada franja horaria Si no ocurrió ningún evento durante ese intervalo, el conteo es
0
. -
Asegúrese de controlar los límites superior e inferior correctamente:
- Resultados inesperados de una consulta SQL con marcas de tiempo BETWEEN
-
La función de ventana
lead()
tiene una característica que a menudo se pasa por alto:puede proporcionar un valor predeterminado para cuando no existe una fila inicial. Proporcionar'infinity'
en el ejemplo. De lo contrario, el último intervalo se cortaría con un límite superiorNULL
.
Equivalente mínimo
La consulta anterior usa un CTE y lead()
y sintaxis detallada. Elegante y quizás más fácil de entender, pero un poco más caro. Aquí hay una versión mínima, más corta y más rápida:
SELECT start_time, count(e.ts) AS events
FROM (SELECT generate_series(min(ts), max(ts), interval '17 min') FROM event) g(start_time)
LEFT JOIN event e ON e.ts >= g.start_time
AND e.ts < g.start_time + interval '17 min'
GROUP BY 1
ORDER BY 1;
Ejemplo para "cada 15 minutos en la última semana"`
Y formateando con to_char()
.
SELECT to_char(start_time, 'YYYY-MM-DD HH24:MI'), count(e.ts) AS events
FROM generate_series(date_trunc('day', localtimestamp - interval '7 days')
, localtimestamp
, interval '15 min') g(start_time)
LEFT JOIN event e ON e.ts >= g.start_time
AND e.ts < g.start_time + interval '15 min'
GROUP BY start_time
ORDER BY start_time;
Todavía ORDER BY
y GROUP BY
en la marca de tiempo subyacente valor , no en la cadena formateada. Eso es más rápido y más confiable.
db<>violín aquí
Respuesta relacionada que produce un conteo continuo durante el período de tiempo:
- PostgreSQL:conteo continuo de filas para una consulta 'por minuto'