Postgres - как вернуть строки с 0 отсчета для отсутствующих данных? - PullRequest
13 голосов
/ 06 декабря 2008

Я неравномерно распределил данные (по дате) за несколько лет (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) с помощью модуля календаря.

Есть ли лучший способ сделать это.

Ответы [ 3 ]

22 голосов
/ 31 марта 2013

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

Правильное решение

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. Руководство.

  • Для более общего решения или произвольных временных интервалов рассмотрите этот тесно связанный ответ:

17 голосов
/ 06 декабря 2008

Вы можете создать список всех первых дней прошлого года, скажем, с помощью

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

Тогда вы можете присоединиться к этой серии.

0 голосов
/ 06 декабря 2008

Вы можете создать временную таблицу во время выполнения и оставить соединение на этом. Кажется, это имеет смысл.

...