PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Requête pour le nombre de valeurs distinctes dans une plage de dates glissantes

Scénario de test :

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]`')
;

Requête - renvoie uniquement les jours où une entrée existe dans 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 - revenir tous les jours dans la plage spécifiée, même s'il n'y a pas de lignes pour la journée :

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<>jouez ici

Résultat :

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

Cela a retenti comme un travail pour les fonctions de fenêtre au début, mais je n'ai pas trouvé de moyen de définir le cadre de fenêtre approprié. Aussi, par documentation :

Les fonctions de fenêtre d'agrégation, contrairement aux fonctions d'agrégation normales, n'autorisent pas DISTINCT ou ORDER BY à utiliser dans la liste des arguments de la fonction.

Je l'ai donc résolu avec des sous-requêtes corrélées à la place. Je suppose que c'est la façon la plus intelligente.

BTW, "entre ladite date et il y a 3 jours" serait une période de 4 jours. Votre définition y est contradictoire.

Légèrement plus court, mais plus lent pendant quelques jours :

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;

Connexe :

  • Générer des séries temporelles entre deux dates dans PostgreSQL
  • Nombre glissant de lignes dans un intervalle de temps