web-dev-qa-db-fra.com

Compter le total cumulé dans Postgresql

J'utilise count et group by pour obtenir le nombre d'abonnés enregistrés chaque jour:

  SELECT created_at, COUNT(email)  
    FROM subscriptions 
GROUP BY created at;

Résultat:

created_at  count
-----------------
04-04-2011  100
05-04-2011   50
06-04-2011   50
07-04-2011  300

Je souhaite plutôt obtenir le total cumulé des abonnés chaque jour. Comment est-ce que j'obtiens ceci?

created_at  count
-----------------
04-04-2011  100
05-04-2011  150
06-04-2011  200
07-04-2011  500
53
Khairul

Avec des jeux de données plus volumineux, les fonctions de la fenêtre sont le moyen le plus efficace d'effectuer ce type de requêtes - le tableau sera analysé uniquement une fois, au lieu d'une fois pour chaque date, comme le ferait une auto-jointure. Cela semble également beaucoup plus simple. :) PostgreSQL 8.4 et versions ultérieures prennent en charge les fonctions de fenêtre.

Voici à quoi ça ressemble:

SELECT created_at, sum(count(email)) OVER (ORDER BY created_at)
FROM subscriptions
GROUP BY created_at;

Ici OVER crée la fenêtre; ORDER BY created_at Signifie qu'il doit additionner les chiffres dans l'ordre de created_at.


Modifier: Si vous souhaitez supprimer les e-mails en double en une seule journée, vous pouvez utiliser sum(count(distinct email)). Malheureusement, cela ne supprimera pas les doublons qui traversent des dates différentes.

Si vous souhaitez supprimer tous les doublons, je pense que le plus simple est d'utiliser une sous-requête et DISTINCT ON. Cela attribuera les e-mails à leur date la plus ancienne (car je trie par created_at dans l'ordre croissant, il choisira le plus ancien):

SELECT created_at, sum(count(email)) OVER (ORDER BY created_at)
FROM (
    SELECT DISTINCT ON (email) created_at, email
    FROM subscriptions ORDER BY email, created_at
) AS subq
GROUP BY created_at;

Si vous créez un index sur (email, created_at), Cette requête ne devrait pas non plus être trop lente.


(Si vous souhaitez tester, voici comment j'ai créé l'exemple de jeu de données)

create table subscriptions as
   select date '2000-04-04' + (i/10000)::int as created_at,
          '[email protected]' || (i%700000)::text as email
   from generate_series(1,1000000) i;
create index on subscriptions (email, created_at);
88
intgr

Utilisation:

SELECT a.created_at,
       (SELECT COUNT(b.email)
          FROM SUBSCRIPTIONS b
         WHERE b.created_at <= a.created_at) AS count
  FROM SUBSCRIPTIONS a
7
OMG Ponies
SELECT
  s1.created_at,
  COUNT(s2.email) AS cumul_count
FROM subscriptions s1
  INNER JOIN subscriptions s2 ON s1.created_at >= s2.created_at
GROUP BY s1.created_at
2
Andriy M

Je suppose que vous ne voulez qu'une seule ligne par jour et que vous souhaitez toujours afficher les jours sans abonnement (supposons que personne ne s'abonne pour une certaine date, voulez-vous afficher cette date avec le solde de la veille?). Si tel est le cas, vous pouvez utiliser la fonction 'avec':

with recursive serialdates(adate) as (
    select cast('2011-04-04' as date)
    union all
    select adate + 1 from serialdates where adate < cast('2011-04-07' as date)
)
select D.adate,
(
    select count(distinct email)
    from subscriptions
    where created_at between date_trunc('month', D.adate) and D.adate
)
from serialdates D
2
Endy Tjahjono