#sql #postgresql #aggregate-functions
#sql #postgresql #агрегатные функции
Вопрос:
Я использую 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
Ответ №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
порядке.
Редактировать: Если вы хотите удалить повторяющиеся электронные письма в течение одного дня, вы можете использовать sum(count(distinct email))
. К сожалению, это не приведет к удалению дубликатов, которые пересекают разные даты.
Если вы хотите удалить все дубликаты, я думаю, проще всего использовать подзапрос и 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,
'foofoobar@foobar.com' || (i%700000)::text as email
from generate_series(1,1000000) i;
create index on subscriptions (email, created_at);
Комментарии:
1. Это отличный intgr, только в моей таблице подписок содержится много повторяющихся строк электронной почты. Итак, что
over
делается, этоsum
изменениеcount
чисел, но мне все равно нужно пересчитывать уникальные электронные письма на каждую последующую дату.2. Я обновил свой ответ
DISTINCT ON
подзапросом. Это все еще намного быстрее, чем ответ Андрея — может обрабатывать миллион строк в течение нескольких секунд — но, возможно, более сложный.3. Хороший совет по функции generate_series!
4. Обратите внимание, что
DISTINCT ON
это также может быть преобразовано в эквивалентный запрос с помощьюGROUP BY
; в данном случаеSELECT email, MIN(created_at) as created_at FROM subscriptions GROUP BY email
. Какой из них более эффективен, вероятно, будет отличаться, хотя подзапрос с готовой сортировкой изDISTINCT ON
, кажется, дает некоторое преимущество сортировке, необходимой для функции Window.5. Я хотел бы иметь это на ежемесячной основе, как мне нужно изменить этот запрос? У меня с этим реальные проблемы.
Ответ №2:
Использовать:
SELECT a.created_at,
(SELECT COUNT(b.email)
FROM SUBSCRIPTIONS b
WHERE b.created_at <= a.created_at) AS count
FROM SUBSCRIPTIONS a
Ответ №3:
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
Комментарии:
1. Я попробовал
sum(s2.count)
, и консоль выдает ошибку: «вызовы агрегатной функции не могут быть вложенными»2. Я имел в виду, что так и должно быть
COUNT(s2.email)
, извините. Пожалуйста, посмотрите мое отредактированное решение.3. Спасибо, приятель! Я работал с более сложным запросом, и ваша структура проста для понимания (и, следовательно, для реализации).
Ответ №4:
Я предполагаю, что вам нужна только одна строка в день, и вы хотите по-прежнему показывать дни без каких-либо подписок (предположим, никто не подписывается на определенную дату, вы хотите показать эту дату с балансом предыдущего дня?). Если это так, вы можете использовать функцию ‘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
Комментарии:
1. Спасибо, эта
with
функция тоже может быть полезной. Узнал кое-что новое.2. Вместо serialdates вы можете использовать встроенную функцию:
generate_series(timestamp '2011-04-04', timestamp '2011-04-07', interval '1 day')
Ответ №5:
Для всех, кто видит этот ответ сегодня (2021), вы можете использовать rollup
SELECT created_at, COUNT(email)
FROM subscriptions
GROUP BY rollup(created_at);
это даст вам новую строку с общим
created_at count
-----------------
04-04-2011 100
05-04-2011 50
06-04-2011 50
07-04-2011 300
NULL 500
Вы также можете использовать сводку для частичных результатов, если у вас есть более одного параметра для отображения в вашей группе. Если у вас есть created_by
, например:
SELECT created_at, created_by COUNT(email)
FROM subscriptions
GROUP BY rollup(created_at, created_by);
это даст вам новую строку с общим
created_at created_by count
-----------------------------
04-04-2011 1 80
04-04-2011 2 20
04-04-2021 NULL 100
05-04-2011 1 20
05-04-2011 2 30
05-04-2011 NULL 50
NULL NULL 150
Я взял только цифры за первые два дня, но в этом и заключается идея. он будет отображаться сгруппированным по дате, затем итогом за этот день, затем общим итогом итогов.
Здесь rollup()
имеет значение порядок, касающийся того, как будут отображаться частичные итоги
Комментарии:
1. Это действительно должно быть принятым ответом в 2021 году! В моей системе: Оконная функция (время планирования: 1.134 мс, время выполнения: 1.045 мс), накопительный (время планирования: 0.245 мс, время выполнения: 0.642 мс). Накопительный процесс значительно более производителен.
2. Это не суммируется, как запрашивает OP.
3. у меня не работает накопительный файл.
Ответ №6:
Лучший способ — создать таблицу календаря: calendar (значение даты, месяц, квартал, половина, неделя, год)
Затем вы можете объединить эту таблицу, чтобы составить сводку для нужного вам поля.
Комментарии:
1. Это не имеет ничего общего с получением текущего итога.