Postgres – как вернуть строки с нулевым количеством отсутствующих данных?
-
19-08-2019 - |
Вопрос
У меня есть неравномерно распределенные данные (относительно даты) за несколько лет (2003-2008).Я хочу запросить данные для заданного набора дат начала и окончания, группируя данные по любому из поддерживаемых интервалов (день, неделя, месяц, квартал, год) в PostgreSQL 8.3 (http://www.postgresql.org/docs/8.3/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC).
Проблема в том, что некоторые запросы дают результаты непрерывно в течение необходимого периода времени, как этот:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 77 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 64
2008-01-01 | 31
2008-02-01 | 14
2008-03-01 | 21
2008-04-01 | 28
2008-05-01 | 44
2008-06-01 | 100
2008-07-01 | 72
2008-08-01 | 91
2008-09-01 | 92
2008-10-01 | 79
2008-11-01 | 65
(12 rows)
но некоторые из них пропускают некоторые интервалы из-за отсутствия данных, как этот:
select to_char(date_trunc('month',date), 'YYYY-MM-DD'),count(distinct post_id)
from some_table where category_id=1 and entity_id = 75 and entity2_id = 115
and date <= '2008-12-06' and date >= '2007-12-01' group by
date_trunc('month',date) order by date_trunc('month',date);
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-03-01 | 1
2008-04-01 | 2
2008-06-01 | 1
2008-08-01 | 3
2008-10-01 | 2
(7 rows)
где требуемый набор результатов:
to_char | count
------------+-------
2007-12-01 | 2
2008-01-01 | 2
2008-02-01 | 0
2008-03-01 | 1
2008-04-01 | 2
2008-05-01 | 0
2008-06-01 | 1
2008-07-01 | 0
2008-08-01 | 3
2008-09-01 | 0
2008-10-01 | 2
2008-11-01 | 0
(12 rows)
Счет 0 для отсутствующих записей.
Я видел более ранние обсуждения переполнения стека, но, похоже, они не решают мою проблему, поскольку мой период группировки - один из (день, неделя, месяц, квартал, год) и время выполнения определяется приложением.Поэтому я думаю, что такой подход, как левое соединение с календарной таблицей или таблицей последовательности, не поможет.
Мое текущее решение — заполнить эти пробелы в Python (в приложении Turbogears) с помощью модуля календаря.
Есть лучший способ сделать это.
Решение
Вы можете создать список всех первых дней прошлого года (скажем) с помощью
select distinct date_trunc('month', (current_date - offs)) as date
from generate_series(0,365,28) as offs;
date
------------------------
2007-12-01 00:00:00+01
2008-01-01 00:00:00+01
2008-02-01 00:00:00+01
2008-03-01 00:00:00+01
2008-04-01 00:00:00+02
2008-05-01 00:00:00+02
2008-06-01 00:00:00+02
2008-07-01 00:00:00+02
2008-08-01 00:00:00+02
2008-09-01 00:00:00+02
2008-10-01 00:00:00+02
2008-11-01 00:00:00+01
2008-12-01 00:00:00+01
Тогда вы можете присоединиться к этой серии.
Другие советы
Этот вопрос старый.Но поскольку другие пользователи выбрали его в качестве основного для нового дубликата, я добавляю правильный ответ.
Правильное решение
SELECT *
FROM (
SELECT day::date
FROM generate_series(timestamp '2007-12-01'
, timestamp '2008-12-01'
, interval '1 month') day
) d
LEFT JOIN (
SELECT date_trunc('month', date_col)::date AS day
, count(*) AS some_count
FROM tbl
WHERE date_col >= date '2007-12-01'
AND date_col <= date '2008-12-06'
-- AND ... more conditions
GROUP BY 1
) t USING (day)
ORDER BY day;
Использовать
LEFT JOIN
, конечно.generate_series()
может создавать таблицу временных меток на лету и очень быстро.Как правило, агрегирование происходит быстрее до вы присоединитесь к.Недавно я представил тестовый пример на sqlfiddle.com в этом соответствующем ответе:
В ролях
timestamp
кdate
(::date
) для базового формата.Для большего использованияto_char()
.GROUP BY 1
— это сокращение синтаксиса для ссылки на первый выходной столбец.Может бытьGROUP BY day
также, но это может конфликтовать с существующим столбцом с таким же именем.ИлиGROUP BY date_trunc('month', date_col)::date
но это слишком долго на мой вкус.Работает с доступными аргументами интервала для
date_trunc()
.count()
никогда не производитNULL
(0
без строк), ноLEFT JOIN
делает.
Вернуться0
вместоNULL
во внешнемSELECT
, использоватьCOALESCE(some_count, 0) AS some_count
. Руководство.Для более общее решение или произвольные интервалы времени рассмотрите этот тесно связанный ответ:
Вы можете создать временную таблицу во время выполнения и оставить соединение с ней. Кажется, это имеет смысл.