web-dev-qa-db-ja.com

Postgresqlの累積合計をカウントする

countgroup byを使用して、毎日登録されているサブスクライバーの数を取得しています。

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

結果:

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

代わりに、毎日加入者の累積合計を取得します。どうすれば入手できますか?

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

より大きなデータセットでは、 window functions がこれらの種類のクエリを実行する最も効率的な方法です-テーブルのみがスキャンされます自己結合のように、日付ごとに1回ではなく、1回。また、はるかにシンプルに見えます。 :) PostgreSQL 8.4以降では、ウィンドウ関数がサポートされています。

これは次のようになります。

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

ここで、OVERはウィンドウを作成します。 _ORDER BY created_at_は、カウントを_created_at_の順序で合計する必要があることを意味します。


編集:1日以内に重複するメールを削除する場合は、sum(count(distinct email))を使用できます。残念ながら、これは異なる日付にまたがる重複を削除しません。

all重複を削除したい場合、サブクエリと_DISTINCT ON_を使用するのが最も簡単だと思います。これにより、電子メールは最も早い日付に関連付けられます(created_atで昇順に並べ替えているため、最も早い日付が選択されます)。

_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;
_

_(email, created_at)_にインデックスを作成する場合、このクエリも遅くなりません。


(テストする場合、これがサンプルデータセットの作成方法です)

_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

つかいます:

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

私はあなたが1日に1行だけが必要であり、サブスクリプションなしでまだ日を表示したいと仮定します(特定の日付に誰もサブスクライブしていないと仮定すると、前日の残高でその日付を表示しますか?)この場合、「with」機能を使用できます。

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