Календар, що повторюється / Повторюються події - найкращий спосіб зберігання


311

Я будую спеціальну систему подій, і якщо у вас є повторювана подія, виглядає так:

Подія A повторюється кожні 4 дні, починаючи з 3 березня 2011 року

або

Подія B повторюється кожні 2 тижні у вівторок, починаючи з 1 березня 2011 року

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


Чи можете ви пояснити, чому 1299132000 твердо кодується? Що це буде робити, якщо мені потрібно отримати дати та термін дії користувача для даної кінцевої дати?
Муралі Муругесан

@Murali Boy це стара, але я впевнений, що зараз має бути 1299132000.
Брендон Вамбольдт

@BrandonWamboldt, я спробував вашу ідею з SQL Server. stackoverflow.com/questions/20286332/display-next-event-date . Я хочу знайти всі наступні предмети, як-от c # версія
Billa

Відповіді:


211

Зберігання «простих» повторюваних шаблонів

Для свого календаря на основі PHP / MySQL я хотів якомога ефективніше зберігати повторювану / повторювану інформацію про події. Я не хотів мати велику кількість рядків, і хотів легко знайти всі події, які відбуватимуться в певну дату.

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

Припустимо, що у мене є дві таблиці, одна називається eventsтак:

ID    NAME
1     Sample Event
2     Another Event

І таблиця, яка називається events_metaтак:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1299132000
2     1             repeat_interval_1  432000

З повторним початком - дата без часу як часова мітка unix, а повтор_інтервал - кількість у секундах між інтервалами (432000 - 5 днів).

Повторний_інтервал_1 йде з повторним запуском ідентифікатора 1. Отже, якщо у мене є подія, яка повторюється кожного вівторка та кожного четверга, повторний_інтервал буде 604800 (7 днів), і буде 2 повторення та 2 повторення. Таблиця виглядатиме так:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1298959200 -- This is for the Tuesday repeat
2     1             repeat_interval_1  604800
3     1             repeat_start       1299132000 -- This is for the Thursday repeat
4     1             repeat_interval_3  604800
5     2             repeat_start       1299132000
6     2             repeat_interval_5  1          -- Using 1 as a value gives us an event that only happens once

Потім, якщо у вас є календар, який проходить цикл кожного дня, захоплюючи події за день, у який він перебуває, запит виглядатиме так:

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
LIMIT 0 , 30

Заміна {current_timestamp}часовою позначкою unix на поточну дату (Мінус часу, тому значення години, хвилини та секунди будуть встановлені на 0).

Сподіваємось, це допоможе і комусь іншому!


Зберігання «Комплексних» повторюваних шаблонів

Цей спосіб краще підходить для зберігання складних шаблонів, таких як

Event A repeats every month on the 3rd of the month starting on March 3, 2011

або

Event A repeats Friday of the 2nd week of the month starting on March 11, 2011

Для більшої гнучкості рекомендую поєднувати це з вищевказаною системою. Таблиці для цього мають виглядати так:

ID    NAME
1     Sample Event
2     Another Event

І таблиця, яка називається events_metaтак:

ID    event_id      meta_key           meta_value
1     1             repeat_start       1299132000 -- March 3rd, 2011
2     1             repeat_year_1      *
3     1             repeat_month_1     *
4     1             repeat_week_im_1   2
5     1             repeat_weekday_1   6

repeat_week_imявляє собою тиждень поточного місяця, який потенційно може бути від 1 до 5. repeat_weekdayв день тижня, 1-7.

Тепер припускаючи, що ви перебираєте дні / тижні, щоб створити перегляд місяця у своєму календарі, ви можете скласти такий запит:

SELECT EV . *
FROM `events` AS EV
JOIN `events_meta` EM1 ON EM1.event_id = EV.id
AND EM1.meta_key = 'repeat_start'
LEFT JOIN `events_meta` EM2 ON EM2.meta_key = CONCAT( 'repeat_year_', EM1.id )
LEFT JOIN `events_meta` EM3 ON EM3.meta_key = CONCAT( 'repeat_month_', EM1.id )
LEFT JOIN `events_meta` EM4 ON EM4.meta_key = CONCAT( 'repeat_week_im_', EM1.id )
LEFT JOIN `events_meta` EM5 ON EM5.meta_key = CONCAT( 'repeat_weekday_', EM1.id )
WHERE (
  EM2.meta_value =2011
  OR EM2.meta_value = '*'
)
AND (
  EM3.meta_value =4
  OR EM3.meta_value = '*'
)
AND (
  EM4.meta_value =2
  OR EM4.meta_value = '*'
)
AND (
  EM5.meta_value =6
  OR EM5.meta_value = '*'
)
AND EM1.meta_value >= {current_timestamp}
LIMIT 0 , 30

Це в поєднанні з вищевказаним методом можна поєднувати для покриття більшості повторюваних / повторюваних моделей подій. Якщо я щось пропустив, залиште коментар.


1
Я намагаюся зберігати "Прості" повторювані шаблони. якщо мені це потрібно повторювати щотижня у вівторок, чи потрібно мені змінювати повтор_старт або створити новий запис із останньою датою. чи є спосіб, щоб він повторювався щотижня, грунтуючись на першому повторі_старту ???
loo

1
Ваша відповідь була чудовою допомогою @roguecoder, але це не зовсім спрацювало з рукокрилими ... Я знайшов свою відповідь після цього повідомлення: stackoverflow.com/questions/10545869/…
Бен Сінклер

1
У AND ( ( CASE ( 1299132000 - EM1.meta_value ) WHEN 0 THEN 1 ELSE ( 1299132000 - EM1.meta_value) END ) / EM2.meta_value ) = 1цьому / EM2.meta_valueрозміщено неправильно?
Муралі Муругесан

1
Це чудова допомога. Як би ви запропонували отримати доступ до них як до окремих записів, скажімо, якщо ви хочете мати коментарі чи реєстрацію щодо окремих подій?
johnrees

26
Варто зазначити, що не слід використовувати твердо кодовані значення для повторних інтервалів, тобто 86400секунд в день, тому що це не враховує час літнього часу. Це більш доцільно , щоб обчислити ці речі динамічно на льоту , а замість цього зберігати interval = dailyі interval_count = 1чи interval = monthlyта interval_count = 1.
Corey Ballou

185

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


"Прості" повторювані події

Обробляти події, які повторюються через рівні проміжки часу, такі як:

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-timetamp (1299736800) кожну дату вашого календаря.

Зверніть увагу на використання модуля (знак%). Цей символ є як звичайний поділ, але повертає '' залишок '' замість коефіцієнта, і як такий дорівнює 0, коли поточна дата є точним кратним інтервалу повторення від повторного початку.

Порівняння продуктивності

Це значно швидше, ніж запропонована раніше відповідь "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" для п’ятниці


Сподіваюсь, це допомагає іншим настільки ж, як оригінальна відповідь допомогла мені!


6
Це дивовижно, дякую! Чи маєте ви уявлення про те, як ви кодували "кожні 2 місяці першого понеділка" або "кожні 3 місяці першого понеділка" тощо?
Йордан Лев

6
Я згоден, це дивовижно. Однак я зіткнувся з тією самою дилемою, що й Йордан Лев. Поле "Повтор_інтервал" не корисне для повторних місяців, оскільки деякі місяці довші, ніж інші. Крім того, як обмежити тривалість повторюваної події. Тобто, кожні 2 місяці першого понеділка протягом 8 місяців. У таблиці має бути якась дата закінчення.
Абінаді

11
Це відмінна відповідь. Я кинув дату повторення та додав дату повторення та закінчення, але ця відповідь надзвичайно допомогла.
Ієн Коллінз

3
Порада. Для складних шаблонів можна усунути repeat_intervalстовпчик і представити його у наступних стовпцях (тобто repeat_yearтощо). Для першого ряду ситуацію, що повторюється щопонеділка після 20 травня 2013 року, можна представити, розмістивши 1 у repeat_weekdayі *в інших стовпцях.
musubi

3
@OlivierMATROT @milos Ідея полягає у встановленні поля, яке ви хочете виправити явно, а решта - у підстановці *. Отже, для "кожного місяця на 3-й" ви просто встановите repeat_dayзначення 3, решта repeatполів - * (залишити repeat_intervalнуль) і встановити повторний старт для часового коду unix на 3 березня 2011 року як дату прив’язки.
ahoffner

28

Покращення: замініть часову позначку датою

Як невелике доповнення до прийнятої відповіді, яка згодом була уточнена ahoffner - можна використовувати формат дати, а не позначку часу. Перевагами є:

  1. читабельні дати в базі даних
  2. немає питання з роками> 2038 та часовою міткою
  3. Для видалення потрібно бути обережним із часовими позначками, що базуються на сезонно відрегульованих датах, тобто у Великобританії 28 червня починається на годину раніше, ніж 28 грудня, тому отримання часової позначки з дати може порушити алгоритм рекурсії.

Для цього змініть БД, repeat_startякий буде зберігатися як тип "дата", і repeat_intervalтепер утримуйте дні, а не секунди. тобто 7 на повтор 7 днів.

змінити лінію sql:

WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )

до:

WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)

все інше залишається незмінним. Симплекс!


Що робити, якщо я хочу, щоб моя подія повторювалася з року в рік? Повторний_інтервал повинен зберігати 365 днів? Що робити, якщо у них рік 366?
TGeorge

3
@ George02, якщо подія є щорічною, ви залишаєте повторний_інтервал NULL, а повторний_рік - *, то залежно від рецидиву ви можете встановити повторний місяць і повторний день, наприклад, 11 березня або повторний місяць, повтор_тижня і повтор_ тиждень, щоб встановити другий вівторок квітня.
jerrygarciuh

25

Я б дотримувався цього керівництва: https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md

Також переконайтеся, що ви використовуєте формат iCal, щоб не винаходити колесо і пам’ятайте Правило № 0: НЕ зберігайте окремі екземпляри, що повторюються, як рядки у вашій базі даних!


2
Як би ви змоделювали відстежуючих користувачів, які відвідували конкретний екземпляр? Чи має сенс в цьому випадку відриватися від правила № 0?
Danny Sullivan

2
@DannySullivan Зверху в голові я мав би ще одну сутність attendedEventіз baseInstanceIdі instanceStartDate- Це, наприклад, базова подія, з якої ви створили перегляд календаря повторюваних правил і використовуйте дату початку, щоб вказати інформацію про цей конкретний екземпляр. Тоді ця сутність може також мати щось подібне, attendedListIdщо веде до іншої таблиці id,attendedUserId
Гал Брача

@DannySullivan Я знаю, що минуло деякий час, відколи ти запитав. Але поза попереднім коментарем ви завжди можете зробити зворотний пошук, щоб побачити, чи був цей користувач частиною схеми повторення події. Це скаже вам, якщо вони були принаймні заплановані на подію. Були вони насправді чи ні, це вже інша історія, яка б більше відповідала коментарям DannySullivan.
BRogers

24

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

"КОМПЛЕКСНА ВЕРСІЯ":

події

+ ---------- + ---------------- +
| ID | ІМ’Я |
+ ---------- + ---------------- +
| 1 | Зразок події 1 |
| 2 | Друга подія |
| 3 | Третя подія |
+ ---------- + ---------------- +

події_мета

+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +
| ID | event_id | repe_start | repe_interval | повторити_року | повторити_місяць | repe_day | повторити_тижня | repe_weekday |
+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +
| 1 | 1 | 2014-07-04 | 7 | NULL | NULL | NULL | NULL | NULL |
| 2 | 2 | 2014-06-26 | NULL | 2014 | * | * | 2 | 5 |
| 3 | 3 | 2014-07-04 | NULL | * | * | * | * | 5 |
+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +

Код SQL:

CREATE TABLE IF NOT EXISTS `events` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `NAME` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;

--
-- Dumping data for table `events`
--

INSERT INTO `events` (`ID`, `NAME`) VALUES
(1, 'Sample event'),
(2, 'Another event'),
(3, 'Third event...');

CREATE TABLE IF NOT EXISTS `events_meta` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `event_id` int(11) NOT NULL,
  `repeat_start` date NOT NULL,
  `repeat_interval` varchar(255) NOT NULL,
  `repeat_year` varchar(255) NOT NULL,
  `repeat_month` varchar(255) NOT NULL,
  `repeat_day` varchar(255) NOT NULL,
  `repeat_week` varchar(255) NOT NULL,
  `repeat_weekday` varchar(255) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `ID` (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;

--
-- Dumping data for table `events_meta`
--

INSERT INTO `events_meta` (`ID`, `event_id`, `repeat_start`, `repeat_interval`, `repeat_year`, `repeat_month`, `repeat_day`, `repeat_week`, `repeat_weekday`) VALUES
(1, 1, '2014-07-04', '7', 'NULL', 'NULL', 'NULL', 'NULL', 'NULL'),
(2, 2, '2014-06-26', 'NULL', '2014', '*', '*', '2', '5'),
(3, 3, '2014-07-04', 'NULL', '*', '*', '*', '*', '1');

також доступний як експорт MySQL (для легкого доступу)

Приклад PHP коду index.php:

<?php
    require 'connect.php';    

    $now = strtotime("yesterday");

    $pushToFirst = -11;
    for($i = $pushToFirst; $i < $pushToFirst+30; $i++)
    {
        $now = strtotime("+".$i." day");
        $year = date("Y", $now);
        $month = date("m", $now);
        $day = date("d", $now);
        $nowString = $year . "-" . $month . "-" . $day;
        $week = (int) ((date('d', $now) - 1) / 7) + 1;
        $weekday = date("N", $now);

        echo $nowString . "<br />";
        echo $week . " " . $weekday . "<br />";



        $sql = "SELECT EV.*
                FROM `events` EV
                RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
                WHERE ( DATEDIFF( '$nowString', repeat_start ) % repeat_interval = 0 )
                OR ( 
                    (repeat_year = $year OR repeat_year = '*' )
                    AND
                    (repeat_month = $month OR repeat_month = '*' )
                    AND
                    (repeat_day = $day OR repeat_day = '*' )
                    AND
                    (repeat_week = $week OR repeat_week = '*' )
                    AND
                    (repeat_weekday = $weekday OR repeat_weekday = '*' )
                    AND repeat_start <= DATE('$nowString')
                )";
        foreach ($dbConnect->query($sql) as $row) {
            print $row['ID'] . "\t";
            print $row['NAME'] . "<br />";
        }

        echo "<br /><br /><br />";
    }
?>

Приклад PHP-коду connect.php:

<?
// ----------------------------------------------------------------------------------------------------
//                                       Connecting to database
// ----------------------------------------------------------------------------------------------------
// Database variables
$username = "";
$password = "";
$hostname = ""; 
$database = ""; 

// Try to connect to database and set charset to UTF8
try {
    $dbConnect = new PDO("mysql:host=$hostname;dbname=$database;charset=utf8", $username, $password);
    $dbConnect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

} catch(PDOException $e) {
    echo 'ERROR: ' . $e->getMessage();
}
// ----------------------------------------------------------------------------------------------------
//                                      / Connecting to database
// ----------------------------------------------------------------------------------------------------
?>

Тут також доступний код php (для кращої читабельності):
index.php
та
connect.php.
Налаштування цього налаштування повинно зайняти кілька хвилин. Не години. :)


2
як я можу отримати запит, щоб отримати всі повторювані події в межах дати .. тобто отримати всі повторювані події між 2014-10-01 по 2014-12-30. дякую за ваш пост
Ну Wisher

@Wellwisher - повторіть ... до і тимчасовий стіл stackoverflow.com/questions/34407833/…
Бред Кент

1
@Alex Як я можу видалити один випадок випадків із повторної події.
Пугагенті

1
Я знаю, що це стара тема, але чому тип varchar на стовпцях repe_ *? Чи не можете ви використовувати ціле та від’ємне значення замість '*'?
Олів'є МАТРОТ

1
Дякуємо за код. Але я маю зауважити, що ваша реалізація db / запитів трохи непокоїть і дуже неефективно. Наприклад, навіщо використовувати varchar (255) для таких простих стовпців (як згадується @OlivierMATROT, ви можете використовувати цілі числа, і навіть якщо ні, то чому 255?). А якщо ви повторюєте запит 30 разів, чому б не використовувати оператори чи процедури? Просто кажу заради, якщо хтось збирається це здійснити.
Роні

15

Хоча запропоновані рішення працюють, я намагався реалізувати за допомогою Повного календаря, і це вимагало б понад 90 дзвінків до бази даних для кожного перегляду (оскільки він завантажує поточний, попередній та наступний місяць), що мене не надто хвилювало.

Я знайшов бібліотеку рекурсії https://github.com/tplaner/Коли ви просто зберігаєте правила в базі даних та один запит, щоб витягнути всі відповідні правила.

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

Редагувати: Ця бібліотека призначена для PHP


Я також хочу використовувати fullcalendar. Як коли бібліотека могла мені допомогти? Як витягнути події пропонера?
пірник

@piernik - Я б налаштував бібліотеку, як у документації, і якщо ви стикаєтеся з певними проблемами, відкрийте нове запитання щодо stackoverflow з кодом, який ви встановили, і проблемами, які у вас виникли. Я впевнений, якщо ти докладеш таких зусиль у деяких членів, то допоможе тобі.
Тім Рамсі

Я маю на увазі, що при використанні WhenВи повинні зберігати всі дати запису в базі даних або отримувати всі події повторного запису та генерувати дати в php no в базі даних. Маю рацію?
пірник

@piernik Ви будете зберігати початкову Дату та правило / правила в базі даних і використовувати Whenдля створення всіх дат, які заповнюються з початкової збереженої дати / правил.
Тім Рамсі

Це також не добре - ви не можете в жодному команді mysql отримати правильні події - для цього вам потрібно використовувати PHP. Все одно
дякую

14

Чому б не використати механізм, подібний до завдань Apache cron? http://en.wikipedia.org/wiki/Cron

Для календаря \ планування я використовую дещо інші значення "біт" для розміщення стандартних подій повторного запуску календаря - замість [день тижня (0 - 7), місяць (1 - 12), день місяця (1 - 31), година (0 - 23), хв (0 - 59)]

- Я б використовував щось на кшталт [Рік (повторювати кожні N років), місяць (1 - 12), день місяця (1 - 31), тиждень місяця (1-5), день тижня (0 - 7) ]

Сподіваюсь, це допомагає.


6
Я думаю, що це занадто багато варіантів дня на тиждень. 1–7 або 0–6 видається більш точним.
Абінаді

2
Добре використовувати cron для зберігання повтору. але питання в тому, що це дуже важко знайти.
Стоун

@ Володимир, як ти зберігаєш, кожні два вівторки (кожні два тижні у вівторок)
julestruong

@julestruong цей веб-сайт виглядає так, що на нього є відповідь: coderwall.com/p/yzzu5a/running-a-cron-job-every-other-week
Ештон Вірсдорф

cron має обмежену експресивність, оскільки він без громадянства (просто порівнюючи поточну / гіпотермічну дату / час із закономірністю), як такий, він не може представляти певні загальні ділові / людські зразки, такі як "кожен третій день" або "кожні 7 годин", які вимагають запам'ятати останнє явище. Це не очевидно; ви можете подумати, що ви просто говорите день / 3 або годину / 7 у кронтабі, але потім наприкінці місяця / день у вас є "залишені" дні / години, що менше 3 або 7; з можливими катастрофічними результатами.
Хайме Герреро

5

Я розробив езотеричну мову програмування саме для цього випадку. Найкраще в тому, що вона менш схема і не залежить від платформи. Вам просто потрібно написати програму вибору, для вашого розкладу, синтаксис якої обмежений набором описаних тут правил -

https://github.com/tusharmath/sheql/wiki/Rules

Правила розширюються, і ви можете додати будь-які налаштування, виходячи з тієї логіки повторення, яку хочете виконати, не турбуючись про міграцію схеми тощо.

Це зовсім інший підхід і може мати деякі свої недоліки.


4

Дуже схоже на події MySQL, які зберігаються в системних таблицях. Ви можете подивитися структуру і зрозуміти, які стовпці не потрібні:

   EVENT_CATALOG: NULL
    EVENT_SCHEMA: myschema
      EVENT_NAME: e_store_ts
         DEFINER: jon@ghidora
      EVENT_BODY: SQL
EVENT_DEFINITION: INSERT INTO myschema.mytable VALUES (UNIX_TIMESTAMP())
      EVENT_TYPE: RECURRING
      EXECUTE_AT: NULL
  INTERVAL_VALUE: 5
  INTERVAL_FIELD: SECOND
        SQL_MODE: NULL
          STARTS: 0000-00-00 00:00:00
            ENDS: 0000-00-00 00:00:00
          STATUS: ENABLED
   ON_COMPLETION: NOT PRESERVE
         CREATED: 2006-02-09 22:36:06
    LAST_ALTERED: 2006-02-09 22:36:06
   LAST_EXECUTED: NULL
   EVENT_COMMENT:

4

Стандарт RRULE побудований саме для цієї вимоги, тобто збереження та розуміння повторень. Майкрософт та Google використовують його у своїх подіях у календарі. Будь ласка, перегляньте цей документ для отримання більш детальної інформації. https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html


3

@Rogue Coder

Це чудово!

Ви можете просто використовувати модульну операцію (MOD або% в mysql), щоб зробити кінець простим наприкінці:

Замість:

AND (
    ( CASE ( 1299132000 - EM1.`meta_value` )
        WHEN 0
          THEN 1
        ELSE ( 1299132000 - EM1.`meta_value` )
      END
    ) / EM2.`meta_value`
) = 1

Зробіть:

$current_timestamp = 1299132000 ;

AND ( ('$current_timestamp' - EM1.`meta_value` ) MOD EM2.`meta_value`) = 1")

Якщо взяти це далі, можна включити події, які не повторюються назавжди.

Для позначення дати останнього "повторного інтервалу_1" може бути додано щось на кшталт "repeat_interval_1_end". Однак це робить запит складнішим, і я не можу зрозуміти, як це зробити ...

Може хтось міг допомогти!


1

Два наведені вами приклади дуже прості; їх можна представити як простий інтервал (перший - чотири дні, другий - 14 днів). Як ви будете моделювати це, буде повністю залежати від складності рецидивів. Якщо те, що у вас є вище, справді таке просто, то зберігайте дату початку та кількість днів у інтервалі повтору.

Якщо, однак, вам потрібно підтримувати такі речі

Подія A повторюється щомісяця 3 числа місяця, починаючи з 3 березня 2011 року

Або

Подія A повторюється в другу п’ятницю місяця, починаючи з 11 березня 2011 року

Тоді це набагато складніший зразок.


1
Я змусив додати більш складні правила, які ви тільки що виклали пізніше, але поки не. Як би я змоделював SQL-запит для отримання подій, скажімо, 7 березня 2011 року, щоб він отримав мою повторювану подію?
Брендон Вамбольдт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.