Powtarzające się / powtarzające się wydarzenia w kalendarzu - najlepsza metoda przechowywania

311

Buduję niestandardowy system zdarzeń, a jeśli masz powtarzające się zdarzenie, które wygląda następująco:

Wydarzenie A powtarza się co 4 dni, począwszy od 3 marca 2011 r

lub

Wydarzenie B powtarza się co 2 tygodnie we wtorek, począwszy od 1 marca 2011 r

Jak mogę przechowywać to w bazie danych w sposób, który ułatwiłby wyszukiwanie. Nie chcę problemów z wydajnością, jeśli istnieje duża liczba wydarzeń, i muszę przejrzeć każde z nich podczas renderowania kalendarza.

Brandon Wamboldt
źródło
Czy możesz wyjaśnić, dlaczego 1299132000 jest zakodowane na stałe? Co to zrobi, jeśli będę musiał uzyskać daty wystąpienia i użytkownika dla danej daty końcowej?
Murali Murugesan
@Murali Boy to stary, ale jestem pewien, że 1299132000 ma być bieżącą datą.
Brandon Wamboldt
@BrandonWamboldt, próbowałem twój pomysł z SQL Server. stackoverflow.com/questions/20286332/display-next-event-date . Chcę znaleźć wszystkie kolejne elementy, takie jak wersja c #
Billa

Odpowiedzi:

211

Przechowywanie „prostych” powtarzalnych wzorów

W moim kalendarzu opartym na PHP / MySQL chciałem przechowywać informacje o powtarzających się / cyklicznych zdarzeniach tak efektywnie, jak to możliwe. Nie chciałem mieć dużej liczby wierszy i chciałem łatwo wyszukać wszystkie wydarzenia, które miałyby miejsce w określonym dniu.

Poniższa metoda jest świetna do przechowywania powtarzających się informacji, które pojawiają się w regularnych odstępach czasu, takich jak codziennie, co n dni, co tydzień, co miesiąc każdego roku itp. Obejmuje to również wzorce typów we wtorki i czwartki, ponieważ są one przechowywane osobno, jak co tydzień, począwszy od wtorku i co tydzień, począwszy od czwartku.

Zakładając, że mam dwie tabele, z których jedna nazywa się eventstak:

ID    NAME
1     Sample Event
2     Another Event

I tabela o nazwie events_metatak:

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

Powtarzanie_startu jest datą bez czasu jako uniksowego znacznika czasu, a powtarzanie_interwału w sekundach między interwałami (432000 to 5 dni).

Powtórz_interval_1 idzie z powtórzeniem_startu identyfikatora 1. Więc jeśli mam zdarzenie, które powtarza się w każdy wtorek i każdy czwartek, powtórzenie_interwału wynosiłoby 604800 (7 dni), i byłyby 2 powtórzenia_startu i 2 powtórzenia. Tabela wyglądałaby następująco:

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

Następnie, jeśli masz kalendarz, który przechodzi przez każdy dzień, chwytając wydarzenia z tego dnia, zapytanie wyglądałoby następująco:

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

Zastąpienie {current_timestamp}uniksowym znacznikiem czasu dla bieżącej daty (minus czas, więc godziny, minuty i sekundy zostaną ustawione na 0).

Mam nadzieję, że pomoże to również komuś innemu!


Przechowywanie „złożonych” powtarzalnych wzorców

Ta metoda lepiej nadaje się do przechowywania złożonych wzorów, takich jak

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

lub

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

Polecam połączenie tego z powyższym systemem, aby uzyskać jak największą elastyczność. Tabele do tego powinny wyglądać następująco:

ID    NAME
1     Sample Event
2     Another Event

I tabela o nazwie events_metatak:

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_imoznacza tydzień bieżącego miesiąca, który może wynosić od 1 do 5 potencjalnie. repeat_weekdayw dzień tygodnia, 1-7.

Teraz, zakładając, że przeglądasz dni / tygodnie, aby utworzyć widok miesiąca w swoim kalendarzu, możesz napisać takie zapytanie:

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

To w połączeniu z powyższą metodą można połączyć, aby objąć większość powtarzających się / powtarzających się wzorców zdarzeń. Jeśli coś przeoczyłem, zostaw komentarz.

Brandon Wamboldt
źródło
1
Próbuję zapisać Twoje „proste” powtarzające się wzory. jeśli muszę powtarzać co tydzień we wtorek, czy muszę zmodyfikować funkcję repeat_start lub utworzyć nowy rekord z datą końcową? lub czy istnieje sposób, aby powtarzał się co tydzień na podstawie pierwszego powtórzenia?
loo
1
Twoja odpowiedź była świetną pomocą @roguecoder, ale nie całkiem zadziałała ... Znalazłem swoją odpowiedź po tym poście: stackoverflow.com/questions/10545869/…
Ben Sinclair
1
Czy AND ( ( CASE ( 1299132000 - EM1.meta_value ) WHEN 0 THEN 1 ELSE ( 1299132000 - EM1.meta_value) END ) / EM2.meta_value ) = 1to jest / EM2.meta_valueźle umieszczone?
Murali Murugesan
1
To świetna pomoc. Jak zasugerowałbyś dostęp do nich jako osobnych akt, powiedzmy, jeśli chcesz mieć komentarze lub zameldowania na temat poszczególnych wydarzeń?
johnrees,
26
Warto zauważyć, że nie należy używać wartości zakodowanych na stałe dla powtarzających się interwałów, tj. 86400Sekund dziennie, ponieważ nie uwzględnia to czasu letniego. Bardziej właściwe jest obliczanie tych rzeczy dynamicznie w locie, a zamiast tego przechowywanie interval = dailyi interval_count = 1lub interval = monthlyi interval_count = 1.
Corey Ballou
185

Chociaż obecnie zaakceptowana odpowiedź była dla mnie ogromną pomocą, chciałem udostępnić kilka przydatnych modyfikacji, które upraszczają zapytania i zwiększają wydajność.


„Proste” powtarzanie zdarzeń

Aby obsłużyć zdarzenia powtarzające się w regularnych odstępach czasu, takie jak:

Repeat every other day 

lub

Repeat every week on Tuesday 

Powinieneś utworzyć dwie tabele, z których jedna nazywa się eventstak:

ID    NAME
1     Sample Event
2     Another Event

I tabela o nazwie events_metatak:

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

Z repeat_startbycia UNIX data datownik bez czasu (1369008000 odpowiada do 20 maja 2013) i repeat_intervalilości w sekundach pomiędzy przerwami (604800 wynosi 7 dni).

Pętlując każdy dzień w kalendarzu, możesz uzyskać powtarzające się zdarzenia za pomocą tego prostego zapytania:

SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE  (( 1299736800 - repeat_start) % repeat_interval = 0 )

Wystarczy zastąpić datownikiem uniksowym (1299736800) każdą datę w kalendarzu.

Zwróć uwagę na użycie modulo (znak%). Ten symbol przypomina podział zwykły, ale zwraca iloraz zamiast ilorazu i jako taki wynosi 0, ilekroć bieżąca data jest dokładną wielokrotnością przedziału powtarzania od początku_startu.

Porównanie wydajności

Jest to znacznie szybsze niż poprzednio sugerowana odpowiedź „meta_keys”, która wyglądała następująco:

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

Jeśli uruchomisz WYJAŚNIJ to zapytanie, zauważysz, że wymagało to użycia bufora dołączania:

+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| 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 |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+

Rozwiązanie z 1 złączeniem powyżej nie wymaga takiego bufora.


Wzory „złożone”

Możesz dodać obsługę bardziej złożonych typów, aby obsługiwać te typy reguł powtarzania:

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

lub

Event A repeats second Friday of the month starting on March 11, 2011

Tabela wydarzeń może wyglądać dokładnie tak samo:

ID    NAME
1     Sample Event
2     Another Event

Następnie, aby dodać obsługę tych złożonych reguł, dodaj kolumny, aby 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    

Należy pamiętać, że po prostu trzeba też określić repeat_interval czy zestaw repeat_year, repeat_month, repeat_day, repeat_week, oraz repeat_weekdaydanych.

To sprawia, że ​​wybór obu typów jednocześnie jest bardzo prosty. Wystarczy zapętlić każdy dzień i wpisać poprawne wartości (1370563200 dla 7 czerwca 2013 r., A następnie rok, miesiąc, dzień, numer tygodnia i dzień tygodnia w następujący sposób):

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
  )

Zwraca wszystkie zdarzenia, które powtarzają się w piątek 2. tygodnia, a także wszystkie zdarzenia, które powtarzają się w każdy piątek, więc zwraca zarówno identyfikator zdarzenia 1, jak i 2:

ID    NAME
1     Sample Event
2     Another Event

* Sidenote w powyższym SQL użyłem domyślnych indeksów Dnia PHP , czyli „5” w piątek


Mam nadzieję, że to pomaga innym tak samo jak oryginalna odpowiedź pomogła mi!

ahoffner
źródło
6
To jest niesamowite, dziękuję! Czy masz pojęcie, w jaki sposób kodowałbyś „co 2 miesiące w pierwszy poniedziałek” lub „co 3 miesiące w pierwszy poniedziałek” itp.?
Jordan Lev
6
Zgadzam się, że to niesamowite. Wpadłem jednak w ten sam dylemat, co Jordan Lev. Pole repeat_interval nie nadaje się do powtarzania miesięcy, ponieważ niektóre miesiące są dłuższe niż inne. Jak ograniczyć czas trwania powtarzającego się wydarzenia. Tj. Co 2 miesiące w pierwszy poniedziałek przez 8 miesięcy. Tabela powinna zawierać datę zakończenia.
Abinadi,
11
To doskonała odpowiedź. Upuściłem parametr repeat_interval i dodałem datę repeat_end, ale ta odpowiedź bardzo pomogła.
Iain Collins,
3
Wskazówka: w przypadku złożonych wzorów można wyeliminować repeat_intervalkolumnę i przedstawić ją w kolejnych kolumnach (tj. repeat_yearItp.) W pierwszym rzędzie sytuację powtarzania się w każdy poniedziałek po 20 maja 2013 r. Można przedstawić, umieszczając 1 w polu repeat_weekdayoraz *w pozostałych kolumnach.
musubi
3
@OlivierMATROT @milos Pomysł polega na tym, aby ustawić pole, które chcesz naprawić, a resztę na symbol wieloznaczny *. Tak więc dla „co miesiąc 3-tego” ustawiłeś tylko repeat_day3, pozostałe repeatpola na * (zostaw repeat_intervalnull), i ustaw parametr repeat_start na kod czasowy unix dla 3 marca 2011 r. Jako datę zakotwiczenia.
ahoffner
28

Ulepszenie: zastąp znacznik czasu datą

Jako niewielkie rozszerzenie przyjętej odpowiedzi, które następnie dopracował ahoffner - możliwe jest użycie formatu daty zamiast znacznika czasu. Zalety to:

  1. czytelne daty w bazie danych
  2. brak problemu z latami> 2038 i datownikiem
  3. usuwa należy uważać na znaczniki czasu oparte na datach skorygowanych sezonowo, tj. w Wielkiej Brytanii 28 czerwca rozpoczyna się godzinę wcześniej niż 28 grudnia, więc wyprowadzenie znacznika czasu z daty może złamać algorytm rekurencji.

Aby to zrobić, zmień DB, repeat_startaby był zapisany jako typ „data”, a repeat_intervalteraz przechowuj dni zamiast sekund. tj. 7 powtórzeń z 7 dni.

zmień wiersz sql:

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

do:

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

Cała reszta pozostaje taka sama. Simples!

użytkownik3781087
źródło
Co jeśli chcę, aby moje wydarzenie powtarzało się z roku na rok? repeat_interval powinien przechowywać 365 dni? Co jeśli rok ma 366 dni?
TGeorge
3
@ George02, jeśli wydarzenie jest coroczne, pozostawiasz parametr repeat_interval NULL, a parametr repeat_year ma wartość *, w zależności od tego, jaki cykl wystąpi, możesz ustawić opcję repeat_month i repeat_day, np. 11 marca lub repeat_month, repeat_week i repeat_weekday, aby ustawić drugi wtorek kwietnia.
jerrygarciuh
25

Postąpiłbym zgodnie z tym przewodnikiem: https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md

Upewnij się także, że używasz formatu iCal, aby nie wymyślać koła na nowo, i pamiętaj Regułę 0: NIE przechowuj poszczególnych instancji zdarzeń cyklicznych jako wierszy w bazie danych!

Gal Bracha
źródło
2
Jak byś modelował użytkowników śledzących, którzy uczestniczyli w konkretnym wystąpieniu? Czy w tym przypadku sensowne jest zerwanie z regułą nr 0?
Danny Sullivan
2
@DannySullivan Z góry mojej głowy miałbym inny byt attendedEventz baseInstanceIdi instanceStartDate- To jest na przykład zdarzenie podstawowe, z którego utworzyłeś widok kalendarza reguł cyklicznych i użyj daty początkowej do podania informacji o tym konkretnym wystąpieniu. mieć coś takiego attendedListId, co prowadzi do innej tabeli id,attendedUserId
Gal Bracha
@DannySullivan Wiem, że minęło trochę czasu, odkąd zapytałeś. Ale poza poprzednim komentarzem zawsze można było przeprowadzić wyszukiwanie wsteczne, aby sprawdzić, czy ten użytkownik był częścią wzorca powtarzania zdarzenia. To by powiedziało, gdyby byli przynajmniej zaplanowani na to wydarzenie. To, czy rzeczywiście uczestniczyli, czy nie, to inna historia, która byłaby bardziej zgodna z komentarzem DannySullivana.
BRogers
24

Dla wszystkich, którzy są tym zainteresowani, teraz możesz po prostu skopiować i wkleić, aby rozpocząć w ciągu kilku minut. Skorzystałem z rad w komentarzach tak dobrze, jak mogłem. Daj mi znać, jeśli coś mi umknie.

„WERSJA KOMPLEKSOWA”:

wydarzenia

+ ---------- + ---------------- +
| ID | NAZWA |
+ ---------- + ---------------- +
| 1 | Przykładowe zdarzenie 1 |
| 2 | Drugie wydarzenie |
| 3 | Trzecie wydarzenie |
+ ---------- + ---------------- +

events_meta

+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +
| ID | event_id | repeat_start | repeat_interval | powtórz rok | repeat_month | repeat_day | repeat_week | repeat_weekday |
+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +
| 1 | 1 | 2014-07-04 | 7 | NULL | NULL | NULL | NULL | NULL |
| 2 | 2 | 26.06.2014 | NULL | 2014 | * | * | 2 | 5 |
| 3 | 3 | 2014-07-04 | NULL | * | * | * | * | 5 |
+ ---- + ---------- + -------------- + ------------------ + ------------- + -------------- + ------------ + ------- ------ + ---------------- +

Kod 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');

dostępny również jako eksport MySQL (dla łatwego dostępu)

Przykładowy kod 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 />";
    }
?>

Przykładowy kod 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
// ----------------------------------------------------------------------------------------------------
?>

Również kod php jest dostępny tutaj (dla lepszej czytelności):
index.php
i
connect.php
Teraz konfiguracja zajmie Ci kilka minut. Nie godziny. :)

Alex
źródło
2
Jak mogę zapytać, aby uzyskać wszystkie powtarzające się zdarzenia w zakresie dat .. To znaczy, aby uzyskać wszystkie powtarzające się zdarzenia między 01.10.2014 a 30.12.2014. dzięki za twój post
Well Wisher
@Wellwisher - powtarzaj ... do i tymczasowy stackoverflow.com/questions/34407833/...
Brad Kent
1
@Alex Jak mogę usunąć jedno wystąpienie z powtarzającego się zdarzenia.
Pugazhenthi,
1
Wiem, że jest to stary wątek, ale dlaczego typ varchar w kolumnach repeat_ *? Nie możesz użyć liczby całkowitej i wartości ujemnej zamiast „*”?
Olivier MATROT,
1
Dzięki za kod. Muszę jednak zauważyć, że implementacja db / queries jest nieco niepokojąca i bardzo nieefektywna. Na przykład dlaczego używać varchar (255) do takich prostych kolumn (jak wspomniano @OlivierMATROT, możesz użyć liczb całkowitych, a nawet jeśli nie, dlaczego 255?). A jeśli powtarzasz zapytanie 30 razy, dlaczego nie skorzystać z instrukcji lub procedur? Mówię tylko dla dobra, jeśli ktoś ma zamiar to zaimplementować.
Rony,
15

Podczas gdy proponowane rozwiązania działają, starałem się wdrożyć z pełnym kalendarzem i wymagałoby to ponad 90 wywołań bazy danych dla każdego widoku (ponieważ ładuje bieżący, poprzedni i następny miesiąc), co nie byłem zbyt zachwycony.

Znalazłem bibliotekę rekurencyjną https://github.com/tplaner/, w której po prostu przechowujesz reguły w bazie danych i jedno zapytanie do pobrania wszystkich odpowiednich reguł.

Mam nadzieję, że pomoże to komuś innemu, ponieważ spędziłem tyle godzin, szukając dobrego rozwiązania.

Edycja: ta biblioteka jest dla PHP

Tim Ramsey
źródło
Chcę też użyć fullcalendar. Jak biblioteka może mi pomóc? Jak wyodrębnić zdarzenia propoera?
piernik
@piernik - Ustawiłbym bibliotekę jak w dokumentacji, a jeśli napotykasz określone problemy, otwórz nowe pytanie dotyczące przepływu stosu z ustawionym kodem i problemami, które masz. Jestem pewien, że jeśli włożysz tyle wysiłku w niektórych członków, to ci pomoże.
Tim Ramsey,
Mam na myśli, że używając Whenmusisz zapisać wszystkie daty odzyskania w bazie danych lub uzyskać wszystkie zdarzenia odzyskiwania i wygenerować daty w php no w bazie danych. Czy mam rację?
piernik
@piernik Pierwszą datę i regułę należy zapisać w bazie danych i użyć Whendo wygenerowania wszystkich dat - które są wypełniane od początkowej przechowywanej daty / reguł.
Tim Ramsey,
To też nie jest dobre - Nie możesz w jednym poleceniu mysql uzyskać poprawnych zdarzeń - Do tego musisz użyć PHP. W każdym razie dzięki
piernik
14

Dlaczego nie skorzystać z mechanizmu podobnego do zadań Apache cron? http://en.wikipedia.org/wiki/Cron

W przypadku kalendarza \ harmonogramu użyłbym nieco innych wartości dla „bitów”, aby uwzględnić standardowe zdarzenia powtarzania kalendarza - zamiast [dzień tygodnia (0–7), miesiąc (1–12), dzień miesiąca (1–31), godzina (0–23), min (0–59)]

- Użyłbym czegoś takiego jak [Rok (powtarzaj co N lat), miesiąc (1–12), dzień miesiąca (1–31), tydzień miesiąca (1-5), dzień tygodnia (0–7) ]

Mam nadzieję że to pomoże.

Vladimir
źródło
6
Myślę, że to zbyt wiele opcji dnia tygodnia. 1-7 lub 0-6 wydaje się dokładniejsze.
Abinadi,
2
Dobrze jest użyć crona do przechowywania powtórzeń. ale problem polega na tym, że bardzo trudno jest wyszukać.
Kamienisty
@Vladimir, jak przechowywać, co dwa wtorek (co dwa tygodnie we wtorek)
julestruong
@julestruong ta strona wygląda tak, jakby miała odpowiedź: coderwall.com/p/yzzu5a/running-a-cron-job-every-other-week
Ashton Wiersdorf
cron ma ograniczoną ekspresyjność, ponieważ jest bezpaństwowy (jedynie porównując bieżącą / hipotetyczną datę / godzinę ze wzorcem), jako taki, nie może reprezentować pewnych typowych biznesowych / ludzkich wzorców, takich jak „co trzeci dzień” lub „co 7 godzin”, które wymagają zapamiętania ostatniego wystąpienia. To nie jest oczywiste; możesz pomyśleć, że po prostu wypowiadasz dzień / 3 lub godzinę / 7 w crontab, ale pod koniec miesiąca / dnia masz „resztki” dni / godzin, które są mniejsze niż 3 lub 7; z możliwymi katastrofalnymi skutkami.
Jaime Guerrero
5

Właśnie dla tego przypadku opracowałem ezoteryczny język programowania. Najlepsze w tym jest to, że nie ma schematu i jest niezależny od platformy. Musisz tylko napisać program selektora, zgodnie ze swoim harmonogramem, którego składnia jest ograniczona zestawem opisanych tutaj reguł -

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

Reguły są rozszerzalne i można dodawać dowolne dostosowania w oparciu o logikę powtarzania, którą chcesz wykonać, bez obawy o migrację schematu itp.

Jest to zupełnie inne podejście i może mieć swoje wady.

Tusharmath
źródło
4

Brzmi bardzo podobnie do zdarzeń MySQL przechowywanych w tabelach systemowych. Możesz spojrzeć na strukturę i dowiedzieć się, które kolumny nie są potrzebne:

   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:
Valentin Kuzub
źródło
4

Standard RRULE został zbudowany właśnie dla tego wymogu, tj. Zapisywania i zrozumienia nawrotów. Zarówno Microsoft, jak i Google używają go w swoich wydarzeniach w kalendarzu. Przejrzyj ten dokument, aby uzyskać więcej informacji. https://icalendar.org/iCalendar-RFC-5545/3-8-5-3-recurrence-rule.html

Ravinder_Chahal
źródło
3

@Rogue Coder

To jest świetne!

Możesz po prostu użyć operacji modulo (MOD lub% w mysql), aby na końcu uprościć kod:

Zamiast:

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

Zrobić:

$current_timestamp = 1299132000 ;

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

Aby pójść dalej, można uwzględnić zdarzenia, które nie powracają na zawsze.

Można dodać coś takiego jak „repeat_interval_1_end” w celu oznaczenia daty ostatniego „repeat_interval_1”. Powoduje to jednak, że zapytanie jest bardziej skomplikowane i nie mogę naprawdę wymyślić, jak to zrobić ...

Może ktoś mógłby pomóc!

dorogz
źródło
1

Dwa podane przykłady są bardzo proste; mogą być reprezentowane jako prosty interwał (pierwszy to cztery dni, drugi to 14 dni). Sposób modelowania będzie zależeć całkowicie od złożoności twoich nawrotów. Jeśli to, co masz powyżej, jest naprawdę takie proste, zapisz datę początkową i liczbę dni w odstępie powtarzania.

Jeśli jednak musisz wspierać takie rzeczy

Wydarzenie A powtarza się co miesiąc 3 dnia miesiąca, począwszy od 3 marca 2011 r

Lub

Wydarzenie A powtarza się w drugi piątek miesiąca, począwszy od 11 marca 2011 r

To jest o wiele bardziej złożony wzór.

Adam Robinson
źródło
1
Dodałem bardziej złożone zasady, które właśnie określiłeś w późniejszym momencie, ale na razie nie. Jak mam modelować zapytanie SQL, aby uzyskać zdarzenia, powiedzmy 7 marca 2011 r., Aby uzyskać moje powtarzające się zdarzenie?
Brandon Wamboldt,