Разработка календарной системы, такой как Google Calendar

Мне нужно создать что-то похожее на Календарь Google, поэтому я создал таблицу событий, содержащую все события для пользователя.

Самая сложная часть - это обработка повторяющихся событий, строка в таблице событий имеет поле event_type, которое сообщает вам, что это за событие, поскольку событие может быть только для одной даты ИЛИ повторяющееся событие каждые x дней. .

Основная проблема дизайна - это обработка повторяющихся событий.

Когда пользователь просматривает календарь, используя представление месяца, как я могу отобразить все события за данный месяц? Запрос будет сложным, поэтому я подумал, что будет проще создать другую таблицу и создать строку для каждого события, включая повторяющиеся события.

Что вы ребята думаете?

Ответов (16)

Исходя из прошлого опыта, я бы создавал новую запись для каждого происходящего события, а затем имел бы столбец, который ссылается на предыдущее событие, чтобы вы могли отслеживать все события в серии.

У этого есть два преимущества:

  1. Никаких сложных процедур для определения даты следующего мероприятия
  2. Отдельные вхождения можно редактировать, не влияя на остальные

Надеюсь, это даст вам пищу для размышлений :)

Я занимаюсь именно этой проблемой, и я полностью использовал iCalendar ( rfc 2445 ) до тех пор, пока не прочитал эту ветку, поэтому я понятия не имею, насколько хорошо это будет или не будет интегрироваться с этим. В любом случае дизайн, который я придумал, выглядит примерно так:

  • Вы не можете хранить все экземпляры повторяющегося события, по крайней мере, до того, как они произойдут, поэтому у меня просто есть одна таблица, в которой первый экземпляр события хранится как фактическая дата, необязательный срок действия и поля repeat_unit и repeat_increment, допускающие значение NULL. описать повтор. Для единичных экземпляров поля повторения равны нулю, в противном случае единицами измерения будут «день», «неделя», «месяц», «год», а приращение - это просто кратное количество единиц, которое нужно добавить к дате начала для следующего вхождения.
  • Сохранение прошлых событий кажется преимуществом только в том случае, если вам нужно установить отношения с другими сущностями в вашей модели, и даже тогда нет необходимости иметь явную таблицу «экземпляров событий» в каждом случае. Если у других сущностей уже есть данные "экземпляра" даты / времени, то, скорее всего, будет достаточно внешнего ключа для события (или таблицы соединения для "многие-ко-многим").
  • Чтобы сделать «изменить этот экземпляр» / «изменить все будущие экземпляры», я планировал просто продублировать события и удалить устаревшие. Таким образом, чтобы изменить один экземпляр, вы должны истечь старый в его последнем появлении, сделать копию для нового, уникального экземпляра с изменениями и без каких-либо повторений, а еще одну копию оригинала в следующем экземпляре, который повторяется в будущее. Изменение всех будущих экземпляров аналогично, вы просто истечете срок действия оригинала и создадите новую копию с изменениями и деталями повторения.

На данный момент я вижу две проблемы с этим дизайном:

  1. Это затрудняет представление событий типа MWF. Это возможно, но вынуждает пользователя создавать три отдельных события, которые повторяются еженедельно в M, W, F индивидуально, и любые изменения, которые они хотят внести, также должны быть сделаны для каждого отдельно. Подобные события не особо полезны в моем приложении, но они оставляют бородавку на модели, что делает ее менее универсальной, чем хотелось бы.
  2. Копируя события для внесения изменений, вы нарушаете связь между ними, что может быть полезно в некоторых сценариях (или, может быть, это может быть просто иногда проблематично). Теоретически таблица событий может содержать поле идентификатора «copied_from», чтобы отслеживать, где находится событие возникло, но я еще не до конца продумал, насколько полезным может быть что-то подобное. С одной стороны, иерархические отношения родитель / потомок сложно запрашивать из SQL, поэтому преимущества должны быть довольно значительными, чтобы перевесить затраты на запрос этих данных. Я полагаю, вы могли бы использовать вместо этого вложенный набор .

Наконец, я думаю, что можно вычислять события для заданного промежутка времени, используя простой SQL, но я не проработал точных деталей и думаю, что запросы обычно оказываются слишком громоздкими, чтобы иметь смысл. Однако ради аргумента вы можете использовать следующее выражение для вычисления разницы в месяцах между заданным месяцем и годом события:

(:month + (:year * 12)) - (MONTH(occursOn) + (YEAR(occursOn) * 12))

Основываясь на последнем примере, вы можете использовать MOD, чтобы определить, является ли разница в месяцах правильным множителем:

MOD(:month + (:year * 12)) - (MONTH(occursOn) + (YEAR(occursOn) * 12), repeatIncrement) = 0

В любом случае это не идеально (оно не игнорирует события с истекшим сроком действия, не учитывает время начала / окончания события и т. Д.), Поэтому это только как мотивирующий пример. Вообще говоря, я думаю, что большинство запросов окажутся слишком сложными. Вероятно, вам лучше запрашивать события, которые происходят в заданном диапазоне или не истекают раньше диапазона, и вычислять сами экземпляры в коде, а не в SQL. Если вы действительно хотите, чтобы база данных выполняла обработку, хранимая процедура, вероятно, значительно упростит вам жизнь.

В стартовом комплекте Ra-Ajax Calendar есть пример обработки события RenderDate, которое может изменять даты, в частности. Хотя «повторяющиеся события» - это скорее алгоритм, и здесь я сомневаюсь, что очень немногие календари вам сильно помогут ...

undefined написал:

... по этому столу будет очень тяжело, особенно если я собираюсь рассылать электронные письма, чтобы напоминать людям об их событиях (события могут быть и в определенное время дня!), Так что я мог бы опрашивать эту таблицу каждые 15 минут потенциально !

Создайте таблицу для уведомлений. Опрос только его.

Обновляйте таблицу уведомлений при обновлении событий (повторяющихся или иных).

РЕДАКТИРОВАТЬ: представление базы данных может не нарушать нормальные формы, если это вызывает беспокойство. Но вы, вероятно, захотите отслеживать, какие уведомления были отправлены, а какие еще не были отправлены.

Я бы сказал, начнем с ical стандарта. Если вы используете его в качестве модели, вы сможете делать все, что есть в календаре Google, Outlook, macical (программе), и получить практически мгновенную интеграцию с ними.

А теперь пора поработать с ajax и javascript, потому что у вас не может быть яркого веб-интерфейса с перетаскиванием и нескольких календарей без тонны ajax и javascript.

Я должен согласиться с @ChanChan прочитать спецификацию ical о том, как хранить эти вещи. Нет простого способа справиться с повторениями, особенно с исключениями. Я построил, перестроил и перестроил базу данных, чтобы справиться с этим, и продолжаю возвращаться к ical.

Создание подчиненной таблицы - неплохая идея, в зависимости от вариантов использования. Алгоритм расчета именно при возникновении. . . гм, случается. . . действительно может быть довольно сложным. От его запуска никуда не деться, но стоит подумать о кешировании результатов.

Думаю, я понимаю, что ваш второй абзац означает, что вы рассматриваете вторую таблицу событий, в которой есть строка для каждого возникновения события. Я бы этого не допустил.

Повторяющиеся события должны иметь дату начала и дату окончания (которая может быть нулевой для событий, которые продолжаются каждые X дней «навсегда»). Вам нужно будет решить, какую частоту вы хотите разрешить - каждые X дней, N-й день каждого месяца, каждого дня недели и т. Д.

Я бы, вероятно, склонился к двум таблицам - одна для одноразовых событий, а вторая для повторяющихся событий. Вам нужно будет запросить и отобразить два отдельно.

Если бы я собирался заняться этим (и я бы изо всех сил старался не изобретать это колесо), я бы поискал библиотеки с открытым исходным кодом или, по крайней мере, проекты с открытым исходным кодом с календарями, на которые вы можете посмотреть . Какие-нибудь рекомендации, ребята?

У вас должна быть дата начала, дата окончания и срок действия. Однодневные события будут иметь одинаковую дату начала и дату окончания, а также позволит вам выполнять события неполного дня. Что касается повторяющихся событий, то даты начала и окончания будут для одного и того же дня, но имеют разное время, тогда у вас есть перечисление или таблица, в которой указывается частота повторения (ежедневно, еженедельно, ежемесячно и т. Д.).

Это позволяет вам сказать «это событие появляется каждый день» для ежедневного, «это событие появляется во 2-й день каждой недели» для еженедельно, «это событие появляется 5-го числа каждого месяца» для «ежемесячно», это событие появляется на 215-й день каждого года "ежегодно, если дата меньше даты истечения срока.

@GateKiller

Я не думал о случае, когда вы редактируете отдельные вхождения. В этом случае имеет смысл хранить вхождения отдельно.

Однако, когда вы это сделаете, как далеко в будущем вы сохраните события? Вы выбираете произвольную дату? Вы автоматически создаете новые вхождения, когда пользователь впервые просматривает будущие месяцы / годы?

Кроме того, как поступить в случае, когда пользователь хочет отредактировать всю серию. «У нас встреча каждый вторник утром в 10:30, но мы собираемся начать встречу в среду в 8»

Даррен,

Вот как я на самом деле разработал свою таблицу событий, но если подумать, скажем, у меня есть 100 тысяч пользователей, которые создают события, эта таблица сильно пострадает, особенно если я собираюсь рассылать электронные письма, чтобы напомнить людям об их события (события также могут быть для определенного времени дня!), поэтому я мог бы опрашивать эту таблицу каждые 15 минут!

Вот почему я хотел создать другую таблицу, которая бы расширяла все события / повторяющиеся события, таким образом я могу просто нажать эту таблицу и получить просмотр событий пользователей за месяцы без выполнения каких-либо сложных запросов и бизнес-логики, И это будет сделать опрос гораздо более эффективным.

Вопрос в том, должна ли эта вторичная таблица быть на следующий день или месяц? Что имеет больше смысла? Поскольку максимум, который может просмотреть пользователь, - это просмотр по месяцам, я склоняюсь к таблице, в которой записаны все события за данный месяц.

(конечно, мне придется поддерживать эту вторичную таблицу для любых изменений, которые пользователь может внести в исходную таблицу событий).

Чанчан,

На самом деле я разработал его с такой же функциональностью, но я просто говорю о том, как я буду хранить события, в частности, как обрабатывать повторяющиеся события.

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

Вы можете попытаться сгенерировать будущие события по запросу и заполнить будущие события только тогда, когда это необходимо, чтобы отобразить их или отправить уведомление о них. Однако, если вы все равно собираетесь создавать код генерации «по запросу», почему бы не использовать его постоянно? Вместо того, чтобы извлекать из таблицы событий и затем использовать генерацию событий по запросу для заполнения любых новых событий, которые еще не были добавлены в таблицу, просто используйте исключительно генерацию событий по запросу. Конечный результат будет таким же. При использовании этой схемы вам нужно сохранить только даты начала и окончания, а также частоту событий.

Я не вижу способа избежать генерации событий по запросу, поэтому я не вижу утилиту в таблице событий. Если вы хотите это ради кеширования, то, думаю, вы принимаете неправильный подход. Во-первых, это плохой кеш, потому что вы все равно не можете избежать генерации событий по запросу. Во-вторых, вам, вероятно, в любом случае следует кэшировать на более высоком уровне. Если вы хотите кешировать, то кешируйте сгенерированные страницы, а не события.

Что касается повышения эффективности вашего опроса, если вы проводите опрос только каждые 15 минут, а ваша база данных и / или сервер не могут справиться с нагрузкой, тогда вы уже обречены. Ваша база данных не сможет обрабатывать пользователей, если она не может обрабатывать гораздо более частые опросы, не беспокоясь об этом.

Дерек Парк, я бы создавал каждый экземпляр события в таблице, и эта таблица обновлялась бы каждый месяц (так что любое событие, которое было настроено на повторение «вечно», было бы регенерировано за месяц вперед с использованием службы Windows или возможно, на уровне сервера sql). Опрос будет проводиться не только каждые 15 минут, это может быть только для опросов, связанных с уведомлениями по электронной почте. Когда кто-то захочет просмотреть свои события в течение месяца, мне придется проанализировать все их события, а также повторяющиеся события и выяснить, какие события отображать (поскольку повторяющееся событие могло быть создано 6 месяцев назад, но относится к месяц, который просматривает пользователь).

Зак, меня не слишком заботит наличие идеально нормализованной базы данных, тот факт, что я думаю о создании вторичной таблицы, уже нарушает одно из правил, хе-хе. Таблицы моей основной базы данных следуют «правилам», но я не возражаю против создания дополнительных таблиц / столбцов время от времени, когда это улучшает производительность.

Грубым, но все же разумным способом было бы создать новую строку в вашей таблице одиночных событий для каждого экземпляра повторяющегося события, все указывающее не на событие, предшествующее ему в серии, а на первое событие в серии. Это упрощает выбор и / или удаление всех элементов в определенной серии, поскольку вы можете выбирать на основе родительского идентификатора. Это также позволяет пользователям удалять отдельные элементы из серии, не затрагивая остальные.

Этот запрос дает вам серию, которая начинается с элемента 3:

SELECT * FROM events WHERE id = 3 OR parentid = 3

Чтобы получить все товары за этот месяц, если у вас есть дата начала и дата окончания в таблице событий, все, что вам нужно сделать, это:

SELECT * FROM events WHERE startdate >= '2008-08-01' AND enddate <= '2008-08-31'

Программная обработка создания / модификации серий не будет очень сложной, но на самом деле это будет зависеть от набора функций, который вы хотите предоставить, и от того, как, по вашему мнению, он будет использоваться. Если вы хотите различать серии и события, у вас может быть отдельная таблица серий и обнуляемый series_id для ваших событий, что позволит вам свободно возиться с отдельными событиями, сохраняя при этом контроль над своей серией.

Как уже говорилось ранее, не изобретайте велосипед , просто улучшайте его.

Checkout VCalendar , это открытый исходный код, он поставляется на PHP, ASP и ASP.Net (C#)!

Также вы можете проверить Day Pilot, который предлагает календарь, написанный на Asp.Net 2.0. Они предлагают облегченную версию, которую вы можете проверить, и если она вам подходит, вы можете приобрести лицензию.

Обновление (30.09.09):

Если конечно колесо не сломано! Кроме того, вы можете нанести новый блестящий слой краски, если хотите (например, сделать интерфейс лучше). Но, по крайней мере, постарайтесь найти какую-то основу для построения, поскольку календарная система может быть сложной (с повторяющимися событиями), и это делалось тысячи раз.

Вот как я на самом деле разработал свою таблицу событий, но если подумать, скажем, у меня есть 100 тысяч пользователей, которые создают события, эта таблица сильно пострадает, особенно если я собираюсь рассылать электронные письма, чтобы напомнить людям об их события (события также могут быть для определенного времени дня!), поэтому я мог бы опрашивать эту таблицу каждые 15 минут!

Базы данных выполняют задания по исключению обработки наборов данных, поэтому я бы не стал особо беспокоиться об этом. Что вам следует сделать, так это использовать ее в качестве основной таблицы, а затем, по истечении срока действия событий, переместить их в другую таблицу (например, в архив).

Следующее, что вы хотите попробовать, - это как можно меньше запрашивать базу данных , поэтому переместите информацию на уровень кэширования (например, скорость ) и просто сохраните данные в базе данных.

Затем вы можете разделить информацию по нескольким базам данных для масштабирования. т.е. пользователи 1-10000 календарей существуют на сервере 1, 10001-20000 существуют на сервере 2 и т. д.

Вот как я бы масштабировал подобное решение, но я все еще думаю, что оригинальное решение, которое я предложил, - это правильный путь, вопрос только в том, как вы его масштабируете.

Если кто-то занимается Ruby, то есть отличная библиотека Runt, которая делает такие вещи. Стоит проверить. http://runt.rubyforge.org/