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

PostgreSQL:cuenta corriente de filas para una consulta 'por minuto'

Devolver solo minutos con actividad

Más corto

SELECT DISTINCT
       date_trunc('minute', "when") AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY 1;

Usa date_trunc() , te devuelve exactamente lo que necesitas.

No incluyas id en la consulta, ya que desea GROUP BY rebanadas diminutas.

count() se usa típicamente como una función agregada simple. Agregar un OVER cláusula hace que sea una función de ventana. Omitir PARTITION BY en la definición de la ventana:desea un conteo continuo sobre todas las filas . De forma predeterminada, eso cuenta desde la primera fila hasta el último par de la fila actual según lo definido por ORDER BY . El manual:

La opción de encuadre predeterminada es RANGE UNBOUNDED PRECEDING , que es lo mismo que RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW . Con ORDER BY , esto configura el marco para que sean todas las filas desde el inicio de la partición hasta el último ORDER BY de la fila actual compañero.

Y resulta que eso es exactamente lo que necesitas.

Usar count(*) en lugar de count(id) . Se ajusta mejor a su pregunta ("recuento de filas"). Generalmente es ligeramente más rápido que count(id) . Y, aunque podríamos suponer que id es NOT NULL , no se ha especificado en la pregunta, por lo que count(id) es incorrecto , estrictamente hablando, porque los valores NULL no se cuentan con count(id) .

No puedes GROUP BY porciones de minutos en el mismo nivel de consulta. Las funciones agregadas se aplican antes funciones de ventana, la función de ventana count(*) solo vería 1 fila por minuto de esta manera.
Puede, sin embargo, SELECT DISTINCT , porque DISTINCT se aplica después funciones de ventana.

ORDER BY 1 es solo una abreviatura de ORDER BY date_trunc('minute', "when") aquí.
1 es una referencia de referencia posicional a la primera expresión en SELECT lista.

Usa to_char() si necesita formatear el resultado. Me gusta:

SELECT DISTINCT
       to_char(date_trunc('minute', "when"), 'DD.MM.YYYY HH24:MI') AS minute
     , count(*) OVER (ORDER BY date_trunc('minute', "when")) AS running_ct
FROM   mytable
ORDER  BY date_trunc('minute', "when");

Más rápido

SELECT minute, sum(minute_ct) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) sub
ORDER  BY 1;

Muy parecido al anterior, pero:

Uso una subconsulta para agregar y contar filas por minuto. De esta forma obtenemos 1 fila por minuto sin DISTINCT en el exterior SELECT .

Usa sum() como función agregada de ventana ahora para sumar los recuentos de la subconsulta.

Descubrí que esto es sustancialmente más rápido con muchas filas por minuto.

Incluir minutos sin actividad

Más corto

@GabiMe preguntó en un comentario cómo obtener una fila para cada minute en el marco de tiempo, incluidos aquellos en los que no ocurrió ningún evento (sin fila en la tabla base):

SELECT DISTINCT
       minute, count(c.minute) OVER (ORDER BY minute) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (SELECT date_trunc('minute', "when") FROM tbl) c(minute) USING (minute)
ORDER  BY 1;

Genere una fila para cada minuto en el período de tiempo entre el primero y el último evento con generate_series() - aquí directamente basado en valores agregados de la subconsulta.

LEFT JOIN a todas las marcas de tiempo truncadas al minuto y conteo. NULL los valores (donde no existe ninguna fila) no se suman al conteo acumulado.

Más rápido

Con CET:

WITH cte AS (
   SELECT date_trunc('minute', "when") AS minute, count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) 
SELECT m.minute
     , COALESCE(sum(cte.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(min(minute), max(minute), interval '1 min')
   FROM   cte
   ) m(minute)
LEFT   JOIN cte USING (minute)
ORDER  BY 1;

Nuevamente, agregue y cuente filas por minuto en el primer paso, omite la necesidad de DISTINCT posterior .

Diferente de count() , sum() puede devolver NULL . Predeterminado a 0 con COALESCE .

Con muchas filas y un índice en "when" esta versión con una subconsulta fue la más rápida entre un par de variantes que probé con Postgres 9.1 - 9.4:

SELECT m.minute
     , COALESCE(sum(c.minute_ct) OVER (ORDER BY m.minute), 0) AS running_ct
FROM  (
   SELECT generate_series(date_trunc('minute', min("when"))
                        ,                      max("when")
                        , interval '1 min')
   FROM   tbl
   ) m(minute)
LEFT   JOIN (
   SELECT date_trunc('minute', "when") AS minute
        , count(*) AS minute_ct
   FROM   tbl
   GROUP  BY 1
   ) c USING (minute)
ORDER  BY 1;