Подсчет кумулятивного итога в Postgresql - PullRequest
53 голосов
/ 18 апреля 2011

Я использую count и group 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

Ответы [ 5 ]

87 голосов
/ 18 апреля 2011

При больших наборах данных оконные функции являются наиболее эффективным способом выполнения таких запросов - таблица будет сканироваться только один раз, а не один раз для каждой даты, например самостоятельное соединение будет делать. Это также выглядит намного проще. :) 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.


Редактировать: Если вы хотите удалить дубликаты писем в течение одного дня, вы можете использовать sum(count(distinct email)). К сожалению, это не удалит дубликаты, которые пересекают разные даты.

Если вы хотите удалить все дубликаты, я думаю, что проще всего использовать подзапрос и DISTINCT ON. Это будет приписывать электронные письма к их самой ранней дате (поскольку я сортирую по созданному в порядке возрастания, он выберет самую раннюю):

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,
          'foofoobar@foobar.com' || (i%700000)::text as email
   from generate_series(1,1000000) i;
create index on subscriptions (email, created_at);
7 голосов
/ 18 апреля 2011

Использование:

SELECT a.created_at,
       (SELECT COUNT(b.email)
          FROM SUBSCRIPTIONS b
         WHERE b.created_at <= a.created_at) AS count
  FROM SUBSCRIPTIONS a
2 голосов
/ 18 апреля 2011

Я предполагаю, что вам нужна только одна строка в день, и вы все еще хотите показывать дни без каких-либо подписок (предположим, что никто не подписывается на определенную дату, хотите ли вы показать эту дату с балансом предыдущего дня?). Если это так, вы можете использовать функцию «с»:

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 голосов
/ 18 апреля 2011
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
0 голосов
/ 18 июля 2014

Лучше всего иметь таблицу календаря: календарь ( дата дата, месяц инт, четверть, половина int, неделя, год инт )

Затем вы можете присоединиться к этой таблице, чтобы составить сводку для нужного вам поля.

...