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
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);
Verwenden:
SELECT a.created_at,
(SELECT COUNT(b.email)
FROM SUBSCRIPTIONS b
WHERE b.created_at <= a.created_at) AS count
FROM SUBSCRIPTIONS a
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
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