Je construis un système d'événements personnalisé, et si vous avez un événement répétitif qui ressemble à ceci:
L'événement A se répète tous les 4 jours à partir du 3 mars 2011
ou
L'événement B se répète toutes les deux semaines le mardi à compter du 1er mars 2011
Comment puis-je stocker cela dans une base de données de manière à simplifier les recherches? Je ne veux pas de problèmes de performances s’il ya un grand nombre d’événements et je dois les parcourir tous lors du rendu du calendrier.
Pour mon calendrier basé sur PHP/MySQL, je voulais stocker les informations sur les événements récurrents/récurrents aussi efficacement que possible. Je ne voulais pas avoir un grand nombre de lignes et je voulais rechercher facilement tous les événements qui se dérouleraient à une date donnée.
La méthode ci-dessous est idéale pour stocker des informations répétitives qui se produisent à intervalles réguliers, comme tous les jours, tous les n jours, toutes les semaines, tous les mois, etc. elles sont également stockées séparément chaque semaine à partir du mardi et toutes les semaines à partir du jeudi.
En supposant que j'ai deux tables, une appelée events
comme ceci:
ID NAME
1 Sample Event
2 Another Event
Et une table appelée events_meta
comme ceci:
ID event_id meta_key meta_value
1 1 repeat_start 1299132000
2 1 repeat_interval_1 432000
Repeat_start étant une date sans heure comme horodatage unix, et repeat_interval un montant en secondes entre les intervalles (432000 correspond à 5 jours).
repeat_interval_1 correspond à repeat_start de l'ID 1. Ainsi, si j'ai un événement qui se répète tous les mardis et tous les jeudis, repeat_interval serait de 604800 (7 jours), et il y aurait 2 repeat_starts et 2 repeat_intervals. Le tableau ressemblerait à ceci:
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
Ensuite, si vous avez un calendrier qui parcourt tous les jours en saisissant les événements du jour auquel il se présente, la requête se présentera comme suit:
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
Remplacement de {current_timestamp}
par l’horodatage Unix de la date actuelle (Moins l’heure, les valeurs d’heure, de minute et de seconde sont donc réglées sur 0).
J'espère que cela aidera quelqu'un d'autre aussi!
Cette méthode convient mieux au stockage de motifs complexes tels que
Event A repeats every month on the 3rd of the month starting on March 3, 2011
ou
Event A repeats Friday of the 2nd week of the month starting on March 11, 2011
Je recommanderais de combiner cela avec le système ci-dessus pour plus de flexibilité. Les tables pour ceci devraient ressembler à:
ID NAME
1 Sample Event
2 Another Event
Et une table appelée events_meta
comme ceci:
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
représente la semaine du mois en cours, qui pourrait être compris entre 1 et 5 potentiellement. repeat_weekday
dans le jour de la semaine, du 1 au 7.
En supposant maintenant que vous parcouriez les jours/semaines pour créer une vue mensuelle dans votre calendrier, vous pouvez composer une requête comme celle-ci:
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
Ceci combiné avec la méthode ci-dessus pourrait être combiné pour couvrir la plupart des modèles d'événements répétitifs/récurrents. Si j'ai oublié quelque chose s'il vous plaît laissez un commentaire.
Bien que la réponse actuellement acceptée m'aide énormément, je voulais partager quelques modifications utiles qui simplifient les requêtes et augmentent également les performances.
Pour gérer des événements qui se répètent à intervalles réguliers, tels que:
Repeat every other day
ou
Repeat every week on Tuesday
Vous devriez créer deux tables, l'une appelée events
, comme ceci:
ID NAME
1 Sample Event
2 Another Event
Et une table appelée events_meta
comme ceci:
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
Avec repeat_start
étant une date d'horodatage Unix sans heure (1369008000 correspond au 20 mai 2013), et repeat_interval
un montant en secondes entre les intervalles (604800 correspond à 7 jours).
En parcourant chaque jour du calendrier, vous pouvez obtenir des événements répétés en utilisant cette requête simple:
SELECT EV.*
FROM `events` EV
RIGHT JOIN `events_meta` EM1 ON EM1.`event_id` = EV.`id`
WHERE (( 1299736800 - repeat_start) % repeat_interval = 0 )
Remplacez simplement l'horodatage unix (1299736800) par chaque date de votre calendrier.
Notez l'utilisation du modulo (signe%). Ce symbole ressemble à une division normale, mais renvoie le '' reste '' au lieu du quotient, et vaut 0 si la date actuelle est un multiple exact de repeat_interval du repeat_start.
Ceci est nettement plus rapide que la réponse précédemment suggérée "méta_keys", qui était la suivante:
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
Si vous exécutez EXPLAIN, cette requête nécessitera l’utilisation d’un tampon de jointure:
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
| 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 |
+----+-------------+-------+--------+---------------+---------+---------+------------------+------+--------------------------------+
La solution à 1 jointure ci-dessus ne nécessite aucun tampon de ce type.
Vous pouvez ajouter un support pour des types plus complexes pour prendre en charge ces types de règles de répétition:
Event A repeats every month on the 3rd of the month starting on March 3, 2011
ou
Event A repeats second Friday of the month starting on March 11, 2011
Votre table d'événements peut avoir exactement le même aspect:
ID NAME
1 Sample Event
2 Another Event
Ensuite, pour prendre en charge ces règles complexes, ajoutez des colonnes à events_meta
comme ceci:
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
Notez que vous devez simplement spécifier un repeat_interval
ou un ensemble de repeat_year
, repeat_month
, repeat_day
, repeat_week
et repeat_weekday
données.
Cela rend la sélection des deux types simultanément très simple. Il vous suffit de parcourir chaque jour et de renseigner les valeurs correctes (1370563200 pour le 7 juin 2013, puis l'année, le mois, le jour, le numéro de la semaine et le jour de la semaine comme suit):
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
)
Ceci renvoie tous les événements qui se répètent le vendredi de la deuxième semaine ainsi que tous les événements qui se répètent chaque vendredi. Il renvoie donc à la fois l'ID d'événement 1 et 2:
ID NAME
1 Sample Event
2 Another Event
* Sidenote dans le code SQL ci-dessus j'ai utilisé date de PHP index par défaut du jour de la semaine, donc "5" pour vendredi
J'espère que cela aide les autres autant que la réponse originale m'a aidé!
Comme une petite amélioration à la réponse acceptée qui a ensuite été affinée par ahoffner - il est possible d'utiliser un format de date plutôt que l'horodatage. Les avantages sont:
pour ce faire, modifiez la base de données _ repeat_start
afin qu'elle soit stockée sous le type 'date' et repeat_interval
maintenant des jours plutôt que des secondes. soit 7 pour une répétition de 7 jours.
changer la ligne sql:
WHERE (( 1370563200 - repeat_start) % repeat_interval = 0 )
à:
WHERE ( DATEDIFF( '2013-6-7', repeat_start ) % repeat_interval = 0)
tout le reste reste le même. Simples!
Pour tous ceux qui sont intéressés par ceci, maintenant vous pouvez simplement copier et coller pour commencer en quelques minutes. J'ai pris le conseil dans les commentaires aussi bien que j'ai pu. Dites-moi si je manque quelque chose.
"VERSION COMPLEXE":
événements
+ ---------- + ---------------- + | ID | NOM | + ---------- + ---------------- + | 1 | Exemple d'événement 1 | | 2 | Deuxième événement | | 3 | Troisième événement | + ---------- + ---------------- +
événements_meta
+ ---- + ---------- + -------------- + ------------- ----- + ------------- + -------------- + ------------ + - ----------- + ---------------- + | ID | event_id | repeat_start | repeat_interval | repeat_year | repeat_month | repeat_day | repeat_week | repeat_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 | + ---- + ---------- + -------------- + ----------- ------- + ------------- + -------------- + ------------ + ------------- + ---------------- +
Code 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');
également disponible en tant que exportation MySQL (pour un accès facile)
Exemple de code 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 />";
}
?>
Exemple de code 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
// ----------------------------------------------------------------------------------------------------
?>
Le code php est également disponible ici (pour une meilleure lisibilité):
index.php
et
connect.php
La mise en place devrait prendre quelques minutes. Pas des heures. :)
Je voudrais suivre ce guide: https://github.com/bmoeskau/Extensible/blob/master/recurrence-overview.md
Assurez-vous également que vous utilisez le format iCal afin de ne pas réinventer la roue et rappelez-vous la règle n ° 0: Ne stockez PAS d'instances d'événement récurrentes individuelles sous forme de lignes dans votre base de données!
Pendant que les solutions proposées fonctionnaient, j’essayais d’implémenter Full Calendar et il faudrait plus de 90 appels de base de données pour chaque vue (lorsqu’il charge les versions actuelle, précédente et le mois prochain), ce qui ne me passionne pas trop.
J'ai trouvé une bibliothèque de récursion https://github.com/tplaner/When où vous stockez simplement les règles dans la base de données et une requête pour extraire toutes les règles pertinentes.
J'espère que cela aidera quelqu'un d'autre, car j'ai passé tant de temps à essayer de trouver une bonne solution.
Edit: Cette bibliothèque est pour PHP
Pourquoi ne pas utiliser un mécanisme similaire aux tâches Apache Cron? http://en.wikipedia.org/wiki/Cron
Pour le calendrier et la planification, j'utiliserais des valeurs légèrement différentes pour les "bits" afin de prendre en charge les événements de réapparition du calendrier standard - au lieu de [jour de la semaine (0 - 7), mois (1 - 12), jour du mois (1 - 31), heure (0 - 23), min (0 - 59)]
- J'utiliserais quelque chose comme [Année (répéter tous les N ans), mois (1 - 12), jour du mois (1 - 31), semaine du mois (1-5), jour de la semaine (0 - 7) ]
J'espère que cela t'aides.
J'ai développé un langage de programmation ésotérique juste pour ce cas. La meilleure partie de cela est qu’il est moins schématique et indépendant de la plate-forme. Il vous suffit d'écrire un programme de sélection, pour votre emploi du temps, dont la syntaxe est contrainte par l'ensemble des règles décrites ici -
https://github.com/tusharmath/sheql/wiki/Rules
Les règles sont extensibles et vous pouvez ajouter n'importe quel type de personnalisation en fonction du type de logique de répétition que vous souhaitez effectuer, sans vous soucier des migrations de schéma, etc.
Cette approche est complètement différente et pourrait présenter certains inconvénients.
Cela ressemble beaucoup aux événements MySQL qui sont stockés dans des tables système. Vous pouvez regarder la structure et déterminer quelles colonnes ne sont pas nécessaires:
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:
@Rogue Coder
C'est bien!
Vous pouvez simplement utiliser l'opération modulo (MOD ou% dans mysql) pour simplifier votre code à la fin:
Au lieu de:
AND (
( CASE ( 1299132000 - EM1.`meta_value` )
WHEN 0
THEN 1
ELSE ( 1299132000 - EM1.`meta_value` )
END
) / EM2.`meta_value`
) = 1
Faire:
$current_timestamp = 1299132000 ;
AND ( ('$current_timestamp' - EM1.`meta_value` ) MOD EM2.`meta_value`) = 1")
Pour aller plus loin, on pourrait inclure des événements qui ne se reproduisent plus jamais.
Quelque chose comme "repeat_interval_1_end" pour indiquer la date du dernier "repeat_interval_1" pourrait être ajouté. Cela rend toutefois la requête plus compliquée et je ne peux pas vraiment comprendre comment faire cela ...
Peut-être que quelqu'un pourrait aider!
Les deux exemples que vous avez donnés sont très simples. ils peuvent être représentés comme un simple intervalle (le premier étant de quatre jours, le second de 14 jours). Votre modélisation dépendra entièrement de la complexité de vos récurrences. Si ce que vous avez ci-dessus est vraiment aussi simple que cela, stockez une date de début et le nombre de jours dans l'intervalle de répétition.
Si, toutefois, vous devez soutenir des choses comme
L'événement A se répète tous les mois le 3 du mois à compter du 3 mars 2011
Ou
L'événement A se répète le deuxième vendredi du mois à compter du 11 mars 2011
Ensuite, c'est un modèle beaucoup plus complexe.