Запрос PostgreSQL для подсчета/группировки по дням и отображения дней без данных - программирование
Подтвердить что ты не робот

Запрос PostgreSQL для подсчета/группировки по дням и отображения дней без данных

Мне нужно создать запрос PostgreSQL, который возвращает

  • в день
  • количество найденных объектов за этот день

Важно, чтобы каждый день отображался в результатах, даже если в этот день не было найдено объектов. (Это обсуждалось ранее, но я не смог заставить вещи работать в моем конкретном случае.)

Во-первых, я нашел sql-запрос для создания диапазона дней, с которым я могу присоединиться:

SELECT to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD')
AS date 
FROM generate_series(0, 365, 1) 
AS offs

Результаты в:

    date    
------------
 2013-03-28
 2013-03-27
 2013-03-26
 2013-03-25
 ...
 2012-03-28
(366 rows)

Теперь я пытаюсь присоединиться к таблице с именем "sharer_emailshare", которая имеет столбец "created":

Table 'public.sharer_emailshare'
column    |   type  
-------------------
id        | integer
created   | timestamp with time zone
message   | text
to        | character varying(75)

Вот лучший GROUP BY запрос, который у меня есть до сих пор:

SELECT d.date, count(se.id) FROM (
    select to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD')
    AS date 
    FROM generate_series(0, 365, 1) 
    AS offs
    ) d 
JOIN sharer_emailshare se 
ON (d.date=to_char(date_trunc('day', se.created), 'YYYY-MM-DD'))  
GROUP BY d.date;

Результаты:

    date    | count 
------------+-------
 2013-03-27 |    11
 2013-03-24 |     2
 2013-02-14 |     2
(3 rows)

Желаемые результаты:

    date    | count 
------------+-------
 2013-03-28 |     0
 2013-03-27 |    11
 2013-03-26 |     0
 2013-03-25 |     0
 2013-03-24 |     2
 2013-03-23 |     0
 ...
 2012-03-28 |     0
(366 rows)

Если я правильно понимаю это, потому что я использую простой (подразумеваемый INNER) JOIN, и это ожидаемое поведение, так как обсуждается в документах postgres.

Я просмотрел десятки решений StackOverflow, и все те, у кого есть рабочие запросы, кажутся специфичными для MySQL/Oracle/MSSQL, и мне сложно перевести их на PostgreSQL.

Парень, задающий этот вопрос, нашел свой ответ с помощью Postgres, но положил его на ссылку pastebin, которая истекла некоторое время назад.

Я пытался переключиться на LEFT OUTER JOIN, RIGHT JOIN, RIGHT OUTER JOIN, CROSS JOIN, использовать оператор CASE для sub в другом значении, если null, COALESCE для предоставления значения по умолчанию и т.д., но я не смог использовать их таким образом, чтобы получить то, что мне нужно.

Любая помощь приветствуется! И я обещаю, что скоро приеду к чтению этой гигантской книги PostgreSQL;)

4b9b3361

Ответ 1

Вам просто нужно left outer join вместо внутреннего соединения:

SELECT d.date, count(se.id)
FROM (SELECT to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD') AS date 
      FROM generate_series(0, 365, 1) AS offs
     ) d LEFT OUTER JOIN
     sharer_emailshare se 
     ON d.date = to_char(date_trunc('day', se.created), 'YYYY-MM-DD'))  
GROUP BY d.date;

Ответ 2

Расширяя полезный ответ Гордона Линоффа, я бы предложил пару улучшений, таких как:

  • Используйте ::date вместо date_trunc('day', ...)
  • Присоединяется к типу даты, а не к типу символа (он чище).
  • Используйте определенные диапазоны дат, чтобы их было легче изменить позже. В этом случае я выбираю за год до самой последней записи в таблице - то, что не могло быть легко выполнено с другим запросом.
  • Вычислить итоговые значения для произвольного подзапроса (используя CTE). Вам просто нужно указать интересующий столбец типа даты и назвать его date_column.
  • Включить столбец для общей суммы. (Почему бы и нет?)

Здесь мой запрос:

WITH dates_table AS (
    SELECT created::date AS date_column FROM sharer_emailshare WHERE showroom_id=5
)
SELECT series_table.date, COUNT(dates_table.date_column), SUM(COUNT(dates_table.date_column)) OVER (ORDER BY series_table.date) FROM (
    SELECT (last_date - b.offs) AS date
        FROM (
            SELECT GENERATE_SERIES(0, last_date - first_date, 1) AS offs, last_date from (
                 SELECT MAX(date_column) AS last_date, (MAX(date_column) - '1 year'::interval)::date AS first_date FROM dates_table
            ) AS a
        ) AS b
) AS series_table
LEFT OUTER JOIN dates_table
    ON (series_table.date = dates_table.date_column)
GROUP BY series_table.date
ORDER BY series_table.date

Я протестировал запрос, и он дает те же результаты, плюс столбец для совокупного итога.

Ответ 3

На основании ответа Гордона Линофф я понял, что другая проблема заключалась в том, что у меня было предложение WHERE, о котором я не упоминал в исходном вопросе.

Вместо голой WHERE я сделал подзапрос:

SELECT d.date, count(se.id) FROM (
    select to_char(date_trunc('day', (current_date - offs)), 'YYYY-MM-DD')
    AS date 
    FROM generate_series(0, 365, 1) 
    AS offs
    ) d 
LEFT OUTER JOIN (
    SELECT * FROM sharer_emailshare 
    WHERE showroom_id=5
) se
ON (d.date=to_char(date_trunc('day', se.created), 'YYYY-MM-DD')) 
GROUP BY d.date;

Ответ 4

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

Если вы выполните запрос, подобный следующему:

SELECT series.number FROM generate_series(0, 9) AS series(number)

Вы получите такой вывод:

 number 
--------
      0
      1
      2
      3
      4
      5
      6
      7
      8
      9
(10 rows)

Это можно превратить в такие даты:

SELECT CURRENT_DATE + sequential_dates.date AS date
  FROM generate_series(0, 9) AS sequential_dates(date)

Который выдаст такой результат:

    date    
------------
 2019-09-29
 2019-09-30
 2019-10-01
 2019-10-02
 2019-10-03
 2019-10-04
 2019-10-05
 2019-10-06
 2019-10-07
 2019-10-08
(10 rows)

Затем вы можете выполнить такой запрос (например), присоединив исходный запрос как подзапрос к любой таблице, в которой вы в конечном итоге заинтересованы:

   SELECT sequential_dates.date,
          COUNT(calendar_items.*) AS calendar_item_count
     FROM (SELECT CURRENT_DATE + sequential_dates.date AS date
             FROM generate_series(0, 9) AS sequential_dates(date)) sequential_dates
LEFT JOIN calendar_items ON calendar_items.starts_at::date = sequential_dates.date
 GROUP BY sequential_dates.date

Который выдаст такой результат:

    date    | calendar_item_count 
------------+---------------------
 2019-09-29 |                   1
 2019-09-30 |                   8
 2019-10-01 |                  15
 2019-10-02 |                  11
 2019-10-03 |                   1
 2019-10-04 |                  12
 2019-10-05 |                   0
 2019-10-06 |                   0
 2019-10-07 |                  27
 2019-10-08 |                  24