Cum stochez evenimente recurente în DB? (și cum le interoghez după)

Eu mă refeream la Outlook-ul classic, windows based.

O varianta mai … diferita.

Evenimentul e definit (pe langa alte informatii auxiliare gen cui ii apartine, cum se numeste, etc) de doua valori:

  • O expresie crontab, sir de caractere. Exemplu 0 18 2 * * inseamna “At 18:00 on day-of-month 2.”
  • Durata: timestamp, sau, daca-i prea mare, un int care sa stocheze numarul de secunde.

O functie, sa-i zicem get_recurrences care primeste o expresie crontab, data de start si data de final si care returneaza toate recurentele acelui eveniment ce incep intre data de start si data de final. De exemplu implementata pe baza GitHub - staticlibs/ccronexpr: Cron expression parsing in ANSI C

Asta permite interogari de tipul pentru a afisa cronologic toate recurentele din data de 1 iunie 2022 pentru clientul 1, pentru toate evenimentele sale:

SELECT id, get_recurrences(expression, '2022-06-01', '2022-06-02') AS recurrence_start_date FROM ... WHERE customer_id = 1 ORDER BY recurrence_start_date;

în final am urmat sugestia lui @tekkie (with a twist):

Pentru că nu am doar recurențe de o zi întreagă ci am și evenimente fixe care pot dura câteva ore SAU o zi întreagă, am ales să țin în DB formatul iCalendar (în mod specific VEVENT), care include și detalii de genul DTSTART/DTEND dar care permite și RRULES. Practic pot pune acolo toate detaliile care-mi permit să filtrez chestii mai departe. Tabelul ăsta are event_id | ical | is_past_event | recurrence_type

Cum filtrez mai departe?

Am o tabelă separată, cu date temporare (și temporale :smiley_cat:) care îmi ține informațiile folosite la filtări: start | end | event_id, ce se actualizează în două moduri. (asta e materializarea, presupun)

Primul mod este prin cron, rulat la 24h (urmează să decidem frecvența în viitor, dar momentan cred că chiar și mai rar ar fi ok) astfel încât să avem tot timpul o tabelă cu datele și orele evenimentelor cât-de-cât actuale. Evenimentele cu recurente la nesfârșit sunt materializate pentru următoarele 24 luni.

Cron-ul ignoră evenimentele trecute (select * from events where is_past_event != 1), le iterează pe cele valide și:

  • șterge materializările care nu mai sunt valide
  • generează materializări noi - dacă este cazul
  • marchează evenimentul ca trecut - dacă este cazul

Al doilea mod (twist-ul de la început) este atunci când se actualizează un eveniment [1], materializarea pentru acel eveniment se actualizează direct (fără să mai aștepte până la următorul cron). Diferența dintre acest al doilea mod și cron este că aici ignorăm is_past_event (din simplul motiv că un eveniment trecut poate reîncepe).

În felul ăsta, am ceva aproximativ de genul:

new WP_Query([
  'post_type' => 'event',
  'post__in' => 'select event_id from materialization where start > now()',
  'orderby' => 'post__in'
]);

Care îmi returnează evenimentele ce urmează. Deci great success :smiley:


  1. așa cum am zis, toată povestea se întâmplă într-un WordPress, iar evenimentele sunt post-uri. ↩︎

4 Likes

Zilele astea am avut timp sa experimentez ideea mea.

Date stocate:
schedules

Cum aflu toate recurentele celor doua intrari?

SELECT id, name, recurrence, event_from, event_from + duration AS event_to
FROM (
         SELECT *, get_recurrences(recurrence, start, stop) AS event_from
         FROM schedules
         ORDER BY id) t;

Formatul cron este util pentru recurențe foarte predictibile.

Dar dacă ai recurențe care sunt zilnice/săptămânale exceptând anumite zile, o să vezi că cron este destul de… uhm… limitat.

Ideea e stocarea recurentei intr-un singur rand si posibilitatea de a raspunde la intrebarea care e data urmatoare?

In exemplul de mai sus am invatat baza de date sa raspunda la intrebarea asta folosind expresii cron. Dar se pot alege/inventa si alte formate care sa acopere si exceptii mai complexe.

Ce-am urmarit:

  • Evitarea de tabele temporare
  • Logica ramane in baza de date (si se poate combina usor in interogari complexe)
1 Like

Poti folosi Action Scheduler, oricand e editat event date-ul, setezi prin AS un job as_schedule_single_action si apoi la fiecare rulare / edit, setezi event-ul sa se intample in momentul in care ai nevoie sa creezi urmatorul entry, un exemplu bun e Subscriptiile din WooCommerce.

In total sa fie 1 scheduled action / recurring event. ( pentru a crea child )

1 Like