Хотя принятый в настоящее время ответ мне очень помог, я хотел поделиться некоторыми полезными модификациями, которые упрощают запросы и повышают производительность.
«Простые» повторяющиеся события
Для обработки событий, которые повторяются через равные промежутки времени, например:
Repeat every other day
или
Repeat every week on Tuesday
Вам необходимо создать две таблицы, одна из которых называется events
, например:
ID NAME
1 Sample Event
2 Another Event
И таблица с именем events_meta
выглядит следующим образом:
ID event_id repeat_start repeat_interval
1 1 1369008000 604800 -- Repeats every Monday after May 20th 2013
1 1 1369008000 604800 -- Also repeats every Friday after May 20th 2013
С repeat_start
в качестве даты метки времени Unix без времени (1369008000 соответствует 20 мая 2013 года) и repeat_interval
anколичество в секундах между интервалами (604800 - 7 дней).
Зацикливая каждый день в календаре, вы можете получать повторяющиеся события с помощью этого простого запроса:
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1299736800 - repeat_start) % repeat_interval = 0 )
Просто подставьте в unix-timestamp (1299736800) каждую дату в вашем календаре.
Обратите внимание на использование по модулю (знак%).Этот символ похож на обычное деление, но возвращает «остаток» вместо частного, и поэтому равен 0, когда текущая дата является точным кратным повторения_интервала из repeat_start.
Сравнение производительности
Это значительно быстрее, чем ранее предложенный ответ на основе meta_keys, который был следующим:
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
RIGHT JOIN `events_meta` EM2 ON EM2.`meta_key` = CONCAT( 'repeat_interval_', EM1.`id` )
WHERE EM1.meta_key = 'repeat_start'
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
Если вы запустите EXPLAIN этот запрос, вы заметите, что он требует использованиябуфер объединения:
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| 1 | SIMPLE | EM1 | ALL | NULL | NULL | NULL | NULL | 2 | Using where |
| 1 | SIMPLE | EV | eq_ref | PRIMARY | PRIMARY | 4 | bcs.EM1.event_id | 1 | |
| 1 | SIMPLE | EM2 | ALL | NULL | NULL | NULL | NULL | 2 | Using where; Using join buffer |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
Решение с 1 объединением выше не требует такого буфера.
"Сложные" паттерны
Вы можете добавить поддержку для более сложныхтипы для поддержки следующих типов правил повторения:
Event A repeats every month on the 3rd of the month starting on March 3, 2011
или
Event A repeats second Friday of the month starting on March 11, 2011
Ваша таблица событий может выглядеть точно так же:
ID NAME
1 Sample Event
2 Another Event
Затем добавить поддержкудля этих сложных правил добавьте столбцы к events_meta
следующим образом:
ID event_id repeat_start repeat_interval repeat_year repeat_month repeat_day repeat_week repeat_weekday
1 1 1369008000 604800 NULL NULL NULL NULL NULL -- Repeats every Monday after May 20, 2013
1 1 1368144000 604800 NULL NULL NULL NULL NULL -- Repeats every Friday after May 10, 2013
2 2 1369008000 NULL 2013 * * 2 5 -- Repeats on Friday of the 2nd week in every month
Обратите внимание, что вам просто нужно указать repeat_interval
или набор repeat_year
, repeat_month
, repeat_day
, repeat_week
и repeat_weekday
data.
Это делает выбор обоих типов одновременно очень простым.Просто выполните цикл по каждому дню и введите правильные значения (1370563200 на 7 июня 2013 года, а затем год, месяц, день, номер недели и день недели, как указано ниже):
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )
OR (
(repeat_year = 2013 OR repeat_year = '*' )
AND
(repeat_month = 6 OR repeat_month = '*' )
AND
(repeat_day = 7 OR repeat_day = '*' )
AND
(repeat_week = 2 OR repeat_week = '*' )
AND
(repeat_weekday = 5 OR repeat_weekday = '*' )
AND repeat_start <= 1370563200
)
Возвращает все события, которыеповторить в пятницу 2-й недели , а также любые события, которые повторяются каждую пятницу, поэтому он возвращает оба идентификатора события 1 и 2:
ID NAME
1 Sample Event
2 Another Event
* Sidenote в приведенном выше SQLЯ использовал PHP Date по умолчанию индексы дней недели, так что "5" для пятницы
Надеюсь, это поможет другим так же, как мне помог оригинальный ответ!