Задавайте вопросы, мы ответим
Вы не зашли.
Страниц: 1
Итак, нужно было мне сделать сервис "Расписание". Есть событие, у него есть дата и время начала, дата и время окончания. Плюс, "название" и "тело". Все было хорошо, пока событие было единоразовое. Как только я ввел поняте "периодичность события", уткнулся в то, что не могу сделать хорошую структуру. Или написать хорошие запросы. Если кому-то интересно поковыряться над этим, буду очень признателен.
Итак, структура БД:
mmod_Schedule - данные события, refs_mmod_Schedule_Types - тип события относительно повторения.
1 - Один раз
2 - Ежедневно
3 - Еженедельно
4 - Ежемесячно
Выделение полей в таблице события нужны для построения запросов с учетом периодичности и минимального использования функций преобразования. Так же, я надеялся, что это даст возможность использовать индексы. Вобще, будет понятно по ходу объяснения запросов.
Первая задача: "Вывести все события, которые происходят в текущий момент".
Алгоритм: Для "одноразовых" события - дата начала <= текущей дате, дата окончания >= текущей дате, время начала <= текущего времени, время окончания >= текущего времени. Для "ежедневных": дата начала <= текущей дате, время начала <= текущего времени, время окончания >= текущего времени. Для "еженедельных": день недели события совпадает со текщим днем недели, дата начала <= текущей дате, время начала <= текущего времени, время окончания >= текущего времени.
Для "ежемесячных" все сложнее - если день события равен 31, то в месяцах, где меньше 31 дня событие должно быть выполнено в последний день месяца. Получается: день события равен текущему дню ИЛИ (день в события больше, чем текущий день И текущий день является последним днем месяца). Плюс, не забываем про дата начала <= текущей дате, время начала <= текущего времени, время окончания >= текущего времени.
Если теперь выделить общие части и объяединить все в один запрос, то получается что-то типа:
SELECT s.id, s.Title, s.Body, DATE_FORMAT( s.StartDate, '%d.%m.%y' ) AS StartDate, DATE_FORMAT( s.FinishDate, '%d.%m.%y' ) AS FinishDate, DATE_FORMAT( s.StartTime, '%H:%i' ) AS StartTime, DATE_FORMAT( s.FinishTime, '%H:%i' ) AS FinishTime, s.id_schedule_type FROM mmmod_Schedule s WHERE ( s.StartDate <= CAST( FROM_UNIXTIME( 1235646113 ) AS DATE ) ) AND ( CAST( FROM_UNIXTIME( 1235646113 ) AS TIME ) BETWEEN s.StartTime AND s.FinishTime ) AND ( ( s.id_schedule_type =2 ) OR (s.id_schedule_type =1 AND s.FinishDate >= CAST( FROM_UNIXTIME( 1235646113 ) AS DATE ) ) OR (s.id_schedule_type =3 AND s.DayOfWeek =4 ) OR (( s.id_schedule_type =4 ) AND ((s.NumDay =26) OR ((s.NumDay >26) AND ( 26 =28 ) ) ) ) ) ORDER BY s.StartTime
Запрос строится из PHP на 26.02.2009 14:00, соответственно, значения "1235646113", "26" и "26 = 28" берутся из php.
Индексы я пытался строить всевозможнейшие, но EXPLAIN выглядит очень уныло:
PRIMARY KEY (`id`), KEY `FinishDate` (`FinishDate`), KEY `id_schedule_type` (`id_schedule_type`), KEY `NumDay` (`NumDay`), KEY `NumMonth` (`NumMonth`), KEY `StartDate` (`StartDate`), KEY `Times` (`StartTime`,`FinishTime`), KEY `DayOfWeek` (`DayOfWeek`) ENGINE=MyISAM id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE s ALL FinishDate,id_schedule_type,NumDay,StartDate,DayOf... NULL NULL NULL 25 Using where; Using filesort
Коллеги, есть какие-нибудь мысли, как разгрести этот ком?
P.S. Я здесь разбираю только один запрос, есть еще запрос "получить список событий за период", который по структуре очень похож на данный. И запрос "получить ближайшее событие к заданной дате". Его реализация в виде чистого SQL получилось настолько ужасной, что я предпочел получать список событий на пол года вперед, обрабатывать его в php и выводить ближайшее событие к заданной дате.
P.P.S. Для экспериментов SQL-дамп со структурой и корректными данными Кодировка UTF-8.
Отредактированно Magz (26.02.2009 14:27:01)
Неактивен
Больше всего мне, конечно, нравится «26=28»
Думаю, таки правильное решение будет вытаскивать события на клиентскую сторону.
При этом прошедшие неповторяющиеся события следует помечать как «прошедшие»,
чтобы не вытаскивать заведомо мусор.
Если действительно хотите делать все на уровне базы, то имеет смысл хранить не просто
«начало-конец», а еще дополнительно «начало следующей итерации» и, соответственно,
конец.
При этом нужен EVENT, который будет с дискретностью выставления событий обновлять
«начало итерации» и «конец итерации» в зависимости от времени.
Текущие события при этом получаются действительно простым запросом «сейчас между
началом и концом итерации».
Неактивен
paulus написал:
Больше всего мне, конечно, нравится «26=28»
Это на уровне php подставляется сравнение "сегодняшний день" и "количество дней в этом месяце" Хм... Я же запрос формирую на php, а значит, я могу проверить это в момент формирования и вообще не вставлять эту ветку в запрос! Вот так всегда, хорошая мыслЯ приходит опослЯ
paulus написал:
Думаю, таки правильное решение будет вытаскивать события на клиентскую сторону.
Т.е. вытаскивать вообще все, а в php уже фильтровать и оставлять только нужные?
paulus написал:
При этом прошедшие неповторяющиеся события следует помечать как «прошедшие»,
чтобы не вытаскивать заведомо мусор.
Если действительно хотите делать все на уровне базы, то имеет смысл хранить не просто
«начало-конец», а еще дополнительно «начало следующей итерации» и, соответственно,
конец.
При этом нужен EVENT, который будет с дискретностью выставления событий обновлять
«начало итерации» и «конец итерации» в зависимости от времени.
О, очень интересная идея! В принципе, можно на cron повесить запуск скрипта, который в начале дня будет все это делать. Спасибо, я попробую и обязательно напишу, что получилось
Отредактированно Magz (27.02.2009 11:18:21)
Неактивен
Страниц: 1