Получить количество созданных записей за каждый день

Допустим, у меня есть такой поисковый запрос:

SELECT COUNT(id), date(created_at)
FROM entries
WHERE date(created_at) >= date(current_date - interval '1 week')
GROUP BY date(created_at)

Как вы знаете, например, я получаю такой результат:

count | date
  2   |  15.01.2014
  1   |  13.01.2014
  9   |  09.01.2014

Но я не получаю дни недели, когда записи не создавались.

Как получить такой результат поиска, включая дни, когда не было создано ни одной записи?

count | date
  2   |  15.01.2014
  0   |  14.01.2014
  1   |  13.01.2014
  0   |  12.01.2014
  0   |  11.01.2014
  0   |  10.01.2014
  9   |  09.01.2014

person John Smith    schedule 31.03.2015    source источник


Ответы (4)


SELECT day, COALESCE(ct, 0) AS ct
FROM  (SELECT now()::date - d AS day FROM generate_series (0, 6) d) d  -- 6, not 7
LEFT   JOIN (
   SELECT created_at::date AS day, count(*) AS ct 
   FROM   entries
   WHERE  created_at >= date_trunc('day', now()) - interval '6d'
   GROUP  BY 1
   ) e USING (day);
  • Используйте выражение sargable для условия WHERE, чтобы Postgres мог использовать простой индекс для created_at. Гораздо важнее для производительности, чем все остальное.

  • Чтобы охватить неделю (включая сегодняшний день), вычтите 6 дней из начала «сегодня», а не 7.

  • Предполагая, что id определено NOT NULL, count(*) здесь идентично count(id), но немного быстрее.

  • CTE здесь будет излишним. Это медленнее и более многословно.

  • Сначала объединяйтесь, потом присоединяйтесь. В данном случае это быстрее.

  • now() — это немного более короткая и быстрая реализация Postgres стандартного SQL CURRENT_TIMESTAMP (который вы также можете использовать).

Это должен быть самый короткий и быстрый запрос. Тест с EXPLAIN ANALYZE.

Связанный:

person Erwin Brandstetter    schedule 31.03.2015
comment
Спасибо за Ваш ответ! Если это сработает, я отмечу это как правильное, но в настоящее время я получаю сообщение об ошибке: FEHLER: Unteranfrage in FROM muss Aliasnamen erhalten ZEILE 2: FROM (SELECT now::date - d AS day FROM generate_series (0, ... - person John Smith; 31.03.2015
comment
Я исправил это, используя current date и указав псевдоним для таблицы! Спасибо - person John Smith; 31.03.2015
comment
@ Эрвин Брандштеттер, как count с * быстрее, чем count в одном столбце? Не увеличивается ли ширина строки при выборе * вместо одного столбца в агрегатной функции? - person Kamil Gosciminski; 31.03.2015
comment
@JohnSmith: Да, псевдоним таблицы отсутствовал. - person Erwin Brandstetter; 31.03.2015
comment
@ConsiderMe: при использовании * в count(*) он на самом деле не разбивается на целые строки, он просто обозначает саму строку: для подсчета достаточно простого существования строки, фактическое значение не нужно проверять, это немного быстрее . - person Erwin Brandstetter; 31.03.2015
comment
@ErwinBrandstetter спасибо, приятно знать. Некоторые (в прошлом) спорили со мной о ширине столбцов, которые необходимо подсчитывать, что приводило к повышению производительности при использовании одного столбца в качестве канала для агрегатной функции, такой как COUNT(). - person Kamil Gosciminski; 31.03.2015
comment
@ConsiderMe: Просто запустите тесты на любой большой таблице с помощью EXPLAIN ANALYZE. Разница небольшая. - person Erwin Brandstetter; 31.03.2015
comment
@ErwinBrandstetter, ты прав. Протестировано с * и PK column. Есть небольшая разница в пользу *. Еще раз, спасибо. - person Kamil Gosciminski; 31.03.2015
comment
@ErwinBrandstetter МОЖЕТ, ты тоже поможешь мне с этим? stackoverflow.com/questions/29371305/ - person John Smith; 31.03.2015

Попробуйте этот запрос:

with a as (select current_date - n as dt from generate_series(0, 6) as t(n)),
     b as (select count(id) cnt, date(created_at) created_at
           from entries
           where date(created_at) >= date(current_date - interval '1 week')
           group by date(created_at))
select coalesce(b.cnt,0), a.dt
from a
left join b on (a.dt = b.created_at)
order by a.dt;

Функция count не будет генерировать 0 для несуществующих строк. Таким образом, вы должны заполнить строки для отсутствующих дат. С помощью generate_series и простой арифметики дат вы можете генерировать строки для дат некоторого периода (в данном случае 1 неделя). Затем вы можете выполнить внешнее соединение, чтобы получить окончательный результат. coalesce заменит null на 0.

person ntalbs    schedule 31.03.2015
comment
Работает! А короче ничего нет? - person John Smith; 31.03.2015
comment
Вы можете удалить COALESCE(), COUNT (столбец) не считает значения NULL. COUNT(*) подсчитывает записи и не проверяет наличие NULL. - person Frank Heikens; 31.03.2015

Вам нужно указать SQL обрабатывать NULL. Вернуть 0, если NULL

Вы можете сделать это COALESCE

http://www.postgresql.org/docs/devel/static/functions-conditional.html

person sqluser    schedule 31.03.2015
comment
Спасибо! Но я не понимаю этого полностью, не могли бы вы добавить запрос, пожалуйста, конкретно по моему вопросу? - person John Smith; 31.03.2015
comment
Дата, которой нет, не появится при использовании COALESCE. Это просто не работает. - person Frank Heikens; 31.03.2015

Используйте generate_series() для создания нужных вам дат и ПРИСОЕДИНЯЙТЕСЬ к этому списку дат:

SELECT  COUNT(id), 
    date(gdate)
FROM entries
    RIGHT JOIN generate_series(current_date - interval '1 week', current_date, '1 day') g(gdate) 
    ON date(created_at) = date(gdate) AND date(created_at) >= date(current_date - interval '1 week')
GROUP BY 
    date(gdate)
ORDER BY
    date(gdate) DESC;
person Frank Heikens    schedule 31.03.2015
comment
@JohnSmith: Какие ошибки/проблемы возникают у вас? Должно быть что-то вроде этого, но я не проверял. - person Frank Heikens; 31.03.2015
comment
Я получаю тот же результат, что и раньше! - person John Smith; 31.03.2015
comment
Условие WHERE должно перейти в условие JOIN. Я заменил current_date на фиксированную дату, мой ноутбук уже использует март.... - person Frank Heikens; 31.03.2015
comment
О, этот отличный! Я бы оценил это как правильный ответ, если вы исправите свой запрос, чтобы использовать current_date! Благодарю вас! - person John Smith; 31.03.2015