Хотя принятый в настоящее время ответ мне очень помог, я хотел бы поделиться некоторыми полезными изменениями, которые упрощают запросы и повышают производительность.
«Простые» повторяющиеся события
Для обработки событий, которые повторяются через равные промежутки времени, такие как:
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
также величину в секундах между интервалами (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
данные.
Это делает выбор обоих типов одновременно очень простым. Просто переберите каждый день и введите правильные значения (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» для пятницы
Надеюсь, это поможет другим так же, как мне помог оригинальный ответ!
1299132000
это жестко? Что это будет делать, если мне нужно получить даты появления и пользователя для указанной даты окончания?