SQLinfo.ru - Все о MySQL

Форум пользователей MySQL

Задавайте вопросы, мы ответим

Вы не зашли.

#1 26.02.2009 14:21:51

Magz
Гуру
Откуда: Москва
Зарегистрирован: 18.09.2007
Сообщений: 112

Попытка оптимизировать сервис "Расписание"

Итак, нужно было мне сделать сервис "Расписание". Есть событие, у него есть дата и время начала, дата и время окончания. Плюс, "название" и "тело". Все было хорошо, пока событие было единоразовое. Как только я ввел поняте "периодичность события", уткнулся в то, что не могу сделать хорошую структуру. Или написать хорошие запросы. Если кому-то интересно поковыряться над этим, буду очень признателен.
Итак, структура БД:
http://gololobovo-hram.ru/_files/Schedule_db.jpg
mmod_Schedule - данные события, refs_mmod_Schedule_Types - тип события относительно повторения.
1 - Один раз
2 - Ежедневно
3 - Еженедельно
4 - Ежемесячно
Выделение полей в таблице события нужны для построения запросов с учетом периодичности и минимального использования функций преобразования. Так же, я надеялся, что это даст возможность использовать индексы. Вобще, будет понятно по ходу объяснения запросов. smile

Первая задача: "Вывести все события, которые происходят в текущий момент".
Алгоритм: Для "одноразовых" события - дата начала <= текущей дате, дата окончания >= текущей дате, время начала <= текущего времени, время окончания >= текущего времени. Для "ежедневных": дата начала <= текущей дате, время начала <= текущего времени, время окончания >= текущего времени. Для "еженедельных": день недели события совпадает со текщим днем недели, дата начала <= текущей дате, время начала <= текущего времени, время окончания >= текущего времени.
Для "ежемесячных" все сложнее - если день события равен 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)

Неактивен

 

#2 26.02.2009 17:05:14

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Попытка оптимизировать сервис "Расписание"

Больше всего мне, конечно, нравится «26=28» smile

Думаю, таки правильное решение будет вытаскивать события на клиентскую сторону.
При этом прошедшие неповторяющиеся события следует помечать как «прошедшие»,
чтобы не вытаскивать заведомо мусор.

Если действительно хотите делать все на уровне базы, то имеет смысл хранить не просто
«начало-конец», а еще дополнительно «начало следующей итерации» и, соответственно,
конец.

При этом нужен EVENT, который будет с дискретностью выставления событий обновлять
«начало итерации» и «конец итерации» в зависимости от времени.

Текущие события при этом получаются действительно простым запросом «сейчас между
началом и концом итерации».

Неактивен

 

#3 27.02.2009 11:16:15

Magz
Гуру
Откуда: Москва
Зарегистрирован: 18.09.2007
Сообщений: 112

Re: Попытка оптимизировать сервис "Расписание"

paulus написал:

Больше всего мне, конечно, нравится «26=28» smile

Это на уровне php подставляется сравнение "сегодняшний день" и "количество дней в этом месяце" smile Хм... Я же запрос формирую на php, а значит, я могу проверить это в момент формирования и вообще не вставлять эту ветку в запрос! Вот так всегда, хорошая мыслЯ приходит опослЯ smile

paulus написал:

Думаю, таки правильное решение будет вытаскивать события на клиентскую сторону.

Т.е. вытаскивать вообще все, а в php уже фильтровать и оставлять только нужные?

paulus написал:

При этом прошедшие неповторяющиеся события следует помечать как «прошедшие»,
чтобы не вытаскивать заведомо мусор.

Если действительно хотите делать все на уровне базы, то имеет смысл хранить не просто
«начало-конец», а еще дополнительно «начало следующей итерации» и, соответственно,
конец.

При этом нужен EVENT, который будет с дискретностью выставления событий обновлять
«начало итерации» и «конец итерации» в зависимости от времени.

О, очень интересная идея! В принципе, можно на cron повесить запуск скрипта, который в начале дня будет все это делать. Спасибо, я попробую и обязательно напишу, что получилось smile

Отредактированно Magz (27.02.2009 11:18:21)

Неактивен

 

Board footer

Работает на PunBB
© Copyright 2002–2008 Rickard Andersson