Группа MySQL по дате и количеству, включая отсутствующие даты

Раньше я делал следующее, чтобы получить счет за день из таблицы отчетов.

SELECT COUNT(*) AS count_all, tracked_on
 FROM `reports`
 WHERE (domain_id = 939 AND tracked_on >= '2014-01-01' AND tracked_on <= '2014-12-31')
 GROUP BY tracked_on
 ORDER BY tracked_on ASC;

Очевидно, что это не даст мне 0 счет для отсутствующих дат.

Затем я, наконец, нашел оптимальное решение для создания серии дат между заданным диапазоном дат. Но следующая задача, стоящая перед вами, - это присоединиться к ней со своей таблицей отчетов и получить подсчет, сгруппированный по дате.

select count(*), all_dates.Date as the_date, domain_id
from (
    select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
    from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) all_dates
inner JOIN reports r
    on all_dates.Date >= '2014-01-01'
  and all_dates.Date <= '2014-12-31'
where all_dates.Date between '2014-01-01' and '2014-12-31' AND domain_id = 939 GROUP BY the_date order by the_date ASC ;

В результате получаем

count(*)    the_date    domain_id
46  2014-01-01  939
46  2014-01-02  939
46  2014-01-03  939
46  2014-01-04  939
46  2014-01-05  939
46  2014-01-06  939
46  2014-01-07  939
46  2014-01-08  939
46  2014-01-09  939
46  2014-01-10  939
46  2014-01-11  939
46  2014-01-12  939
46  2014-01-13  939
46  2014-01-14  939
...

<ч/" > В то время как я хочу заполнить недостающие даты с помощью 0

что-то вроде

count(*)    the_date    domain_id
12  2014-01-01  939
23  2014-01-02  939
46  2014-01-03  939
0   2014-01-04  939
0   2014-01-05  939
99  2014-01-06  939
1   2014-01-07  939
5   2014-01-08  939
...

<ч/" > Еще одна попытка, которую я дал, заключалась в следующем:

select count(*), all_dates.Date as the_date, domain_id
from (
    select curdate() - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date
    from (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
    cross join (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as c
) all_dates
inner JOIN reports r
    on all_dates.Date = r.tracked_on
where all_dates.Date between '2014-01-01' and '2014-12-31' AND domain_id = 939 GROUP BY the_date order by the_date ASC ;

Результаты:

count(*)    the_date    domain_id
38        2014-09-03     939
8         2014-09-04     939

Минимальные данные с указанными выше запросами: http://sqlfiddle.com/#!2/dee3e/6

Ответы

Ответ 1

Вам нужно OUTER JOIN, чтобы прибыть каждый день между началом и концом, потому что если вы используете INNER JOIN, он ограничивает вывод только связанными датами (то есть только те даты в таблице отчетов).

Кроме того, когда вы используете OUTER JOIN, вы должны следить за тем, чтобы условия в where clause не вызывали implicit inner join; например И domain_id = 1, если использование в предложении where подавляет любую строку, у которой не было этого условия, но при использовании в качестве условия соединения ограничивает только строки таблицы отчета.

SELECT
      COUNT(r.domain_id)
    , all_dates.Date AS the_date
    , domain_id
FROM (
        SELECT DATE_ADD(curdate(), INTERVAL 2 MONTH) - INTERVAL (a.a + (10 * b.a) ) DAY as Date
        FROM (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
        CROSS JOIN (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
      ) all_dates
      LEFT OUTER JOIN reports r
                  ON all_dates.Date = r.tracked_on
                        AND domain_id = 1
WHERE all_dates.Date BETWEEN '2014-09-01' AND '2014-09-30'
GROUP BY
      the_date
ORDER BY
      the_date ASC;

Я также изменил таблицу, полученную all_dates, используя DATE_ADD(), чтобы продвинуть исходную точку в будущее, и я уменьшил ее размер. Оба варианта являются опциями и могут быть изменены по своему усмотрению.

Демо в SQLfiddle


чтобы прийти к домену_id для каждой строки (как показано в вашем вопросе), вам нужно будет использовать следующее: Обратите внимание, что вы можете использовать IFNULL(), который является специфичным для MySQL, но я использовал COALESCE(), который является более общим SQL. Однако использование @parameter, как показано здесь, в любом случае зависит от MySQL.

SET @domain := 1;

SELECT
      COUNT(r.domain_id)
    , all_dates.Date AS the_date
    , coalesce(domain_id,@domain) AS domain_id
FROM (
        SELECT DATE_ADD(curdate(), INTERVAL 2 month) - INTERVAL (a.a + (10 * b.a) ) DAY as Date
        FROM (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as a
        CROSS JOIN (select 0 as a union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) as b
      ) all_dates
      LEFT JOIN reports r
                  ON all_dates.Date = r.tracked_on
                        AND domain_id = @domain
WHERE all_dates.Date BETWEEN '2014-09-01' AND '2014-09-30'
GROUP BY
      the_date
ORDER BY
      the_date ASC;

См. это в SQLfiddle

Ответ 2

Подзапрос all_dates смотрит только с текущего дня (curdate()). Если вы хотите включить будущие даты, измените первую строку подзапроса на что-то вроде:

select '2015-01-01' - INTERVAL (a.a + (10 * b.a) + (100 * c.a)) DAY as Date