WITH i(i) AS ( -- сначала создадим и заполним таблицы чисел, на их основе будем генерировать много строк путём перемножения множеств SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ), dow(dow_id, dow_name) AS ( -- также создадим таблицу дней недели с номерами дней начиная с нуля (такое значение возвращает функция WEEKDAY) SELECT 0, 'MONDAY' UNION SELECT 1, 'TUESDAY' UNION SELECT 2, 'WEDNESDAY' UNION SELECT 3, 'THURSDAY' UNION SELECT 4, 'FRIDAY' UNION SELECT 5, 'SATURDAY' UNION SELECT 6, 'SUNDAY' ), calendar AS ( /* и заполняем её набором дат с интервалом в 1 день * не забываем также сопоставить дни недели полученных дат, чтобы записать их имена * это пригодится в дальнейшем при сопоставлении с набором recurring_dow, чтобы понять, * был ли в этом дне недели повтор события */ SELECT calendar_date, dow_name FROM ( SELECT start_date + INTERVAL a1000.i*1000 + a100.i*100 + a10.i*10 + a1.i DAY AS calendar_date FROM (SELECT min(recurring_start_date) AS start_date FROM events) AS src, i AS a1, i AS a10, i AS a100, i AS a1000 WHERE (a1000.i*1000 + a100.i*100 + a10.i*10 + a1.i) <= datediff((SELECT max(recurring_end_date) FROM events), now()) ) AS calendar JOIN dow ON WEEKDAY(calendar.calendar_date) = dow.dow_id ORDER BY 1 ) /* теперь сопоставим полученный интервал дат с диапазонами и повторами * смысл в том чтобы найти даты, которые находятся в интервале между повторениями (включая края диапазонов), * и при этом день недели каждой такой даты находится в списке дней недели из recurring_dow * * запрос работает для непустых дат * если нужно записи размножить и удалить те что есть сейчас в таком виде - нужно вставить в таблицу * посчитанные и размноженные записи, а затем удалить те у которых есть заполненные поля дат проведения */ SELECT id, entry_id, title, description, start_time, end_time, color, participants_count, resource_id, recurring, recurring_start_date, recurring_start_time, recurring_end_date, recurring_end_time, recurring_dow, calendar.calendar_date AS recurring_date FROM ( SELECT id, entry_id, title, description, start_time, end_time, color, participants_count, resource_id, recurring, recurring_start_date, recurring_start_time, CASE WHEN recurring_end_date > now() THEN now() ELSE recurring_end_date END AS recurring_end_date, recurring_end_time, recurring_dow FROM events WHERE recurring_start_date <= now() -- начало и конец повторяющегося события может быть в будущем OR (recurring_end_date IS NULL AND end_time <= now()) -- отсеиваем единичные даты из будущего ) AS events LEFT JOIN calendar ON (calendar.calendar_date >= events.recurring_start_date AND calendar.calendar_date <= events.recurring_end_date AND FIND_IN_SET(calendar.dow_name, events.recurring_dow) > 0 ) ORDER BY end_time; -- статистика с подсчётами по годам, месяцам и суммарно WITH slice(start_date_interval, end_date_interval) AS ( SELECT '2020-5-01', '2028-11-13' ), i(i) AS ( -- сначала создадим и заполним таблицы чисел, на их основе будем генерировать много строк путём перемножения множеств SELECT 0 UNION SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 ), dow(dow_id, dow_name) AS ( SELECT 0, 'MONDAY' UNION SELECT 1, 'TUESDAY' UNION SELECT 2, 'WEDNESDAY' UNION SELECT 3, 'THURSDAY' UNION SELECT 4, 'FRIDAY' UNION SELECT 5, 'SATURDAY' UNION SELECT 6, 'SUNDAY' ), calendar AS ( SELECT calendar_date, dow_name FROM ( SELECT start_date_interval + INTERVAL a1000.i*1000 + a100.i*100 + a10.i*10 + a1.i DAY AS calendar_date FROM slice, i AS a1, i AS a10, i AS a100, i AS a1000 WHERE (a1000.i*1000 + a100.i*100 + a10.i*10 + a1.i) <= datediff(end_date_interval, start_date_interval) ) AS calendar JOIN dow ON WEEKDAY(calendar.calendar_date) = dow.dow_id ORDER BY 1 ), summary AS ( SELECT EXTRACT(YEAR FROM events.start_time) AS y, EXTRACT(MONTH FROM events.start_time) AS m, date(events.start_time) AS td, events.* FROM events JOIN slice ON events.start_time >= slice.start_date_interval AND events.end_time <= slice.end_date_interval WHERE recurring_start_date IS NULL UNION ALL SELECT EXTRACT(YEAR FROM calendar.calendar_date) AS y, EXTRACT(MONTH FROM calendar.calendar_date) AS m, date(calendar.calendar_date) AS td, events.* FROM events JOIN calendar ON ( calendar.calendar_date >= events.recurring_start_date AND calendar.calendar_date <= events.recurring_end_date AND FIND_IN_SET(calendar.dow_name, events.recurring_dow) = 1) ) SELECT y "год", m AS "месяц", count(*) AS "количество событий" FROM summary GROUP BY y, m WITH ROLLUP ORDER BY 1, 2;