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

Consulta de recuento de valores distintos en un rango de fechas móviles

Caso de prueba:

CREATE TABLE tbl (date date, email text);
INSERT INTO tbl VALUES
  ('2012-01-01', '[email protected]')
, ('2012-01-01', '[email protected]')
, ('2012-01-01', '[email protected]')
, ('2012-01-02', '[email protected]')
, ('2012-01-02', '[email protected]')
, ('2012-01-03', '[email protected]')
, ('2012-01-04', '[email protected]')
, ('2012-01-05', '[email protected]')
, ('2012-01-05', '[email protected]')
, ('2012-01-06', '[email protected]')
, ('2012-01-06', '[email protected]')
, ('2012-01-06', '[email protected]`')
;

Consulta:devuelve solo los días en los que existe una entrada en tbl :

SELECT date
     ,(SELECT count(DISTINCT email)
       FROM   tbl
       WHERE  date BETWEEN t.date - 2 AND t.date -- period of 3 days
      ) AS dist_emails
FROM   tbl t
WHERE  date BETWEEN '2012-01-01' AND '2012-01-06'  
GROUP  BY 1
ORDER  BY 1;

O - volver todos los días en el rango especificado, incluso si no hay filas para el día:

SELECT date
     ,(SELECT count(DISTINCT email)
       FROM   tbl
       WHERE  date BETWEEN g.date - 2 AND g.date
      ) AS dist_emails
FROM  (SELECT generate_series(timestamp '2012-01-01'
                            , timestamp '2012-01-06'
                            , interval  '1 day')::date) AS g(date);

db<>violín aquí

Resultado:

day        | dist_emails
-----------+------------
2012-01-01 | 3
2012-01-02 | 3
2012-01-03 | 3
2012-01-04 | 3
2012-01-05 | 1
2012-01-06 | 2

Esto sonó como un trabajo para las funciones de ventana al principio, pero no encontré una manera de definir el marco de ventana adecuado. Además, según la documentación:

Las funciones de ventana agregadas, a diferencia de las funciones agregadas normales, no permiten DISTINCT o ORDER BY para ser utilizado dentro de la lista de argumentos de la función.

Así que lo resolví con subconsultas correlacionadas en su lugar. Supongo que esa es la forma más inteligente.

Por cierto, "entre dicha fecha y hace 3 días" sería un período de 4 dias. Su definición es contradictoria allí.

Ligeramente más corto, pero más lento durante algunos días:

SELECT g.date, count(DISTINCT email) AS dist_emails
FROM  (SELECT generate_series(timestamp '2012-01-01'
                            , timestamp '2012-01-06'
                            , interval  '1 day')::date) AS g(date)
LEFT   JOIN tbl t ON t.date BETWEEN g.date - 2 AND g.date
GROUP  BY 1
ORDER  BY 1;

Relacionado:

  • Generando series de tiempo entre dos fechas en PostgreSQL
  • Recuento continuo de filas dentro del intervalo de tiempo