PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Consulta para contagem de valores distintos em um período contínuo


Caso de teste:
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 - retorna apenas os dias em que existe uma entrada em 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;

Ou - retornar todos os dias no intervalo especificado, mesmo se não houver linhas para o dia:
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<>mexa aqui

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

Isso soou como um trabalho para funções de janela no início, mas não encontrei uma maneira de definir o quadro de janela adequado. Além disso, por documentação:

Funções de janela agregadas, ao contrário das funções agregadas normais, não permitem DISTINCT ou ORDER BY para ser usado dentro da lista de argumentos da função.

Então resolvi isso com subconsultas correlacionadas. Acho que essa é a maneira mais inteligente.

BTW, "entre a referida data e 3 dias atrás" seria um período de 4 dias. Sua definição é contraditória aí.

Um pouco mais curto, mas mais lento por alguns dias:
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:
  • Gerando séries temporais entre duas datas no PostgreSQL
  • Contagem contínua de linhas dentro do intervalo de tempo