У меня есть следующий SQL, который проверяет подписки за последние 12 месяцев. Проблема в том, что если подписки в течение определенного месяца нет, она не будет отображаться в результатах. Возьмем, например, следующую структуру для таблицы вакансий:
+------------+---------------------+--------+------------+--+--+
| vacancy_id | create_time | org_id | is_deleted | | |
+------------+---------------------+--------+------------+--+--+
| 1 | 2018-04-06 08:09:48 | 1 | 0 | | |
| 2 | 2018-02-06 08:09:48 | 1 | 0 | | |
| 3 | 2017-08-06 08:09:48 | 1 | 0 | | |
+------------+---------------------+--------+------------+--+--+
и подписки на такие вакансии:
+-----------+-------+------------+
| subscr_id | msg | vacancy_id |
+-----------+-------+------------+
| 1 | test | 1 |
| 2 | test2 | 3 |
+-----------+-------+------------+
Теперь мой запрос таков:
SELECT
YEAR(v.create_time) as vacyear, MONTH(v.create_time) as vacmonth, COUNT(*) as totalsubscriptions
FROM 'vacancies' as v
LEFT JOIN 'vacancy_subscriptions' as vs on v.vacancy_id = vs.vacancy_id
WHERE
(v.create_time >= (DATE_ADD(NOW(),INTERVAL -12 MONTH)))
AND v.is_deleted = 0
AND v.org_id = 1
GROUP BY vacyear, vacmonth
ORDER BY vacyear ASC, vacmonth ASC
Что работает и дает правильный результат в случае, если есть подписка на этот месяц. В этом примере, как я могу позволить выводить все месяцы?
2018-04
2018-03
2018-02
2018-01
2017-12
2017-11
2017-10
2017-9
2017-8
2017-7
2017-6
2017-5
ОБНОВИТЬ:
Я попробовал решение, предложенное ниже пользователем @Gordon Linoff, и создал таблицу, содержащую month_id, с такими именами:
CREATE TABLE 'calendar_months' ( 'month_id' INT(8) NOT NULL, 'en_abbr' VARCHAR(255) NOT NULL , 'en_long' VARCHAR(255) NOT NULL , 'nl_abbr' VARCHAR(255) NOT NULL , 'nl_long' VARCHAR(255) NOT NULL ) ENGINE = InnoDB;
И я вставил данные там вот так:
month_id = 1
en_long = 'January'
...
month_id = 2
en_long = 'February'
...
Затем я изменил запрос, чтобы стать следующим:
SELECT
YEAR(v.create_time) as vacyear, MONTH(v.create_time) as vacmonth, COUNT(*) as totalsubscriptions, cm.nl_long
FROM 'calendar_months' as cm
LEFT JOIN 'vacancies' as v on cm.month_id = month(v.create_time)
LEFT JOIN 'vacancy_subscriptions' as vs on v.vacancy_id = vs.vacancy_id
WHERE
(v.create_time >= (DATE_ADD(NOW(),INTERVAL -12 MONTH)))
AND v.is_deleted = 0
AND v.org_id = 1
GROUP BY vacyear, vacmonth
ORDER BY vacyear ASC, vacmonth ASC
Результат остается тем же
ОБНОВЛЕНИЕ 3:
SELECT
YEAR(v.create_time) as vacyear, MONTH(v.create_time) as vacmonth, COUNT(*) as totalsubscriptions
FROM 'vacancies' as v
LEFT JOIN 'vacancy_subscriptions' as vs on v.vacancy_id = vs.vacancy_id
WHERE
(v.create_time >= (DATE_ADD(NOW(),INTERVAL -12 MONTH)))
AND v.is_deleted = 0
AND v.org_id = 1
GROUP BY vacyear, vacmonth
ORDER BY vacyear ASC, vacmonth ASC
Этот запрос дает правильные результаты:
Если я затем использую запрос, предложенный @Gordon Linoff ниже, то есть:
SELECT YEAR(m.dte) as vacyear, MONTH(m.dte) as vacmonth,
COUNT(vs.vacancy_id) as totalsubscriptions
FROM (SELECT DATE_ADD(NOW(), INTERVAL -12 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -11 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -10 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -9 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -8 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -7 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -6 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -5 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -4 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -3 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -2 MONTH) as dte UNION ALL
SELECT DATE_ADD(NOW(), INTERVAL -1 MONTH) as dte
) as m LEFT JOIN
vacancies v
ON v.create_time >= m.dte AND
v.create_time < m.dte + interval 1 month AND
v.is_deleted = 0 AND v.org_id = 1 LEFT JOIN
vacancy_subscriptions vs
ON v.vacancy_id = vs.vacancy_id
WHERE m.dte >= DATE_ADD(NOW(), INTERVAL -12 MONTH)
GROUP BY vacyear, vacmonth
ORDER BY vacyear ASC, vacmonth ASC;
Я получаю следующий результат (сумма намного ниже, чем должна быть):
Самое безопасное решение - иметь таблицу календаря или использовать производную таблицу с месяцами, которые вы хотите. Тогда это становится первой таблицей в LEFT JOIN
а условия WHERE
переходят в предложение ON
.
Однако во многих случаях WHERE
фильтрует месяцы, которые в противном случае были бы в наборе результатов. Если это имеет место для ваших данных, тогда у вас есть более простое решение. Просто переместите условия WHERE
в SELECT
:
SELECT YEAR(v.create_time) as vacyear, MONTH(v.create_time) as vacmonth,
SUM(v.is_deleted = 0 AND v.org_id = 1) as totalsubscriptions
FROM vacancies v LEFT JOIN
vacancy_subscriptions vs
ON v.vacancy_id = vs.vacancy_id
WHERE v.create_time >= DATE_ADD(NOW(), INTERVAL -12 MONTH)
GROUP BY vacyear, vacmonth
ORDER BY vacyear ASC, vacmonth ASC;
Я должен подчеркнуть, что это не всегда работает - это требует, чтобы каждый месяц интереса имел некоторую запись в вашей таблице. Если это действительно работает, это, как правило, самое простое решение.
РЕДАКТИРОВАТЬ:
В противном случае вам нужно решение, которое является некоторым вариантом:
SELECT YEAR(m.dte) as vacyear, MONTH(m.dte) as vacmonth,
COUNT(vs.vacancy_id) as totalsubscriptions
FROM (SELECT date('2017-05-01') as dte UNION ALL
SELECT date('2017-06-01') as dte UNION ALL
. . .
SELECT date('2018-04-01') as dte
) m LEFT JOIN
vacancies v
ON v.create_time >= m.dte AND
v.create_time < m.dte + interval 1 month AND
v.is_deleted = 0 AND v.org_id = 1 LEFT JOIN
vacancy_subscriptions vs
ON v.vacancy_id = vs.vacancy_id
WHERE m.dte >= DATE_ADD(NOW(), INTERVAL -12 MONTH)
GROUP BY vacyear, vacmonth
ORDER BY vacyear ASC, vacmonth ASC;
"В некотором варианте" учитывается, что у вас может быть другой метод расчета списка месяцев.