web-dev-qa-db-de.com

Kumulative Summe in Postgresql zählen

Ich verwende count und group by, um die Anzahl der Abonnenten pro Tag zu ermitteln:

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

Ergebnis:

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

Ich möchte stattdessen die Gesamtzahl der Abonnenten jeden Tag ermitteln. Wie bekomme ich das?

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

Bei größeren Datenmengen sind Fensterfunktionen die effizienteste Möglichkeit, solche Abfragen durchzuführen - die Die Tabelle wird für jedes Datum nur einmal gescannt, wie dies bei einem Self-Join der Fall wäre. Es sieht auch viel einfacher aus. :) Ab PostgreSQL 8.4 werden Fensterfunktionen unterstützt.

So sieht es aus:

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

Hier erzeugt OVER das Fenster; ORDER BY created_at Bedeutet, dass die Zähler in der Reihenfolge created_at Zusammengefasst werden müssen.


Bearbeiten: Wenn Sie doppelte E-Mails innerhalb eines Tages entfernen möchten, können Sie sum(count(distinct email)) verwenden. Leider werden dadurch keine Duplikate entfernt, die unterschiedliche Daten überschreiten.

Wenn Sie all Duplikate entfernen möchten, ist es meiner Meinung nach am einfachsten, eine Unterabfrage und DISTINCT ON Zu verwenden. Dies ordnet E-Mails dem frühesten Datum zu (da ich nach created_at in aufsteigender Reihenfolge sortiere, wird das früheste ausgewählt):

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;

Wenn Sie einen Index für (email, created_at) Erstellen, sollte diese Abfrage auch nicht zu langsam sein.


(Wenn Sie testen möchten, habe ich den Beispieldatensatz auf diese Weise erstellt.)

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

Verwenden:

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

Ich gehe davon aus, dass Sie nur eine Zeile pro Tag möchten und weiterhin Tage ohne Abonnements anzeigen möchten (Angenommen, niemand abonniert für ein bestimmtes Datum, möchten Sie dieses Datum mit dem Saldo des vorherigen Tages anzeigen?). In diesem Fall können Sie die Funktion 'mit' verwenden:

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