Votre première étape consiste à obtenir les dates de début de votre événement avec chaque événement et l'intervalle de répétition. Pour ce faire, vous pouvez utiliser :
SELECT EventID = e.ID,
e.Name,
StartDateTime = DATEADD(SECOND, rs.Meta_Value, '19700101'),
RepeatInterval = ri.Meta_Value
FROM dbo.Events e
INNER JOIN dbo.Events_Meta rs
ON rs.Event_ID = e.ID
AND rs.Meta_Key = 'repeat_start'
INNER JOIN dbo.Events_Meta ri
ON ri.Event_ID = e.ID
AND ri.Meta_Key = 'repeat_interval_' + CAST(e.ID AS VARCHAR(10));
Cela donne :
EventID | Name | StartDateTime | RepeatInterval
--------+--------------+---------------------+-----------------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800
1 | Billa Vist | 2014-01-04 18:00:00 | 604800
Pour que cela se répète, vous aurez besoin d'une table de nombres pour effectuer une jointure croisée, si vous n'en avez pas, il existe plusieurs façons d'en générer une à la volée, pour des raisons de simplicité, j'utiliserai :
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT Number
FROM Numbers;
Pour en savoir plus, Aaron Bertrand a effectué des comparaisons approfondies des manières de générer des listes séquentielles de nombres :
- Générer un ensemble ou une séquence sans boucles – partie 1
- Générer un ensemble ou une séquence sans boucles – partie 2
- Générer un ensemble ou une séquence sans boucles – partie 3
Si nous limitons notre table de nombres à seulement 0 - 5, et ne regardons que le premier événement, la jonction croisée des deux donnera :
EventID | Name | StartDateTime | RepeatInterval | Number
--------+--------------+---------------------+----------------+---------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800 | 0
1 | Billa Vist | 2014-01-03 10:00:00 | 604800 | 1
1 | Billa Vist | 2014-01-03 10:00:00 | 604800 | 2
1 | Billa Vist | 2014-01-03 10:00:00 | 604800 | 3
1 | Billa Vist | 2014-01-03 10:00:00 | 604800 | 4
1 | Billa Vist | 2014-01-03 10:00:00 | 604800 | 5
Ensuite, vous pouvez obtenir votre occurrence en ajoutant RepeatInterval * Number
à l'heure de début de l'événement :
DECLARE @EndDate DATETIME = '20140130';
WITH EventData AS
( SELECT EventID = e.ID,
e.Name,
StartDateTime = DATEADD(SECOND, rs.Meta_Value, '19700101'),
RepeatInterval = ri.Meta_Value
FROM dbo.Events e
INNER JOIN dbo.Events_Meta rs
ON rs.Event_ID = e.ID
AND rs.Meta_Key = 'repeat_start'
INNER JOIN dbo.Events_Meta ri
ON ri.Event_ID = e.ID
AND ri.Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
), Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT e.EventID,
e.Name,
EventDate = DATEADD(SECOND, n.Number * e.RepeatInterval, e.StartDateTime)
FROM EventData e
CROSS JOIN Numbers n
WHERE DATEADD(SECOND, n.Number * e.RepeatInterval, e.StartDateTime) < @EndDate
ORDER BY e.EventID, EventDate;
Cela donne la sortie attendue :
EVENTID | NAME | EVENTDATE
--------+---------------+--------------------------------
1 | Billa Vist | January, 03 2014 10:00:00+0000
1 | Billa Vist | January, 04 2014 18:00:00+0000
1 | Billa Vist | January, 10 2014 10:00:00+0000
1 | Billa Vist | January, 11 2014 18:00:00+0000
1 | Billa Vist | January, 17 2014 10:00:00+0000
1 | Billa Vist | January, 18 2014 18:00:00+0000
1 | Billa Vist | January, 24 2014 10:00:00+0000
1 | Billa Vist | January, 25 2014 18:00:00+0000
Exemple sur SQL Fiddle
Je pense que le schéma que vous avez est discutable cependant, la jointure sur :
Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
est au mieux fragile. Je pense que vous feriez bien mieux de stocker ensemble la date de début et l'intervalle de répétition qui lui sont associés :
CREATE TABLE dbo.Events_Meta
( ID INT IDENTITY(1, 1) NOT NULL,
Event_ID INT NOT NULL,
StartDateTime DATETIME2 NOT NULL,
IntervalRepeat INT NULL, -- NULLABLE FOR SINGLE EVENTS
RepeatEndDate DATETIME2 NULL, -- NULLABLE FOR EVENTS THAT NEVER END
CONSTRAINT PK_Events_Meta__ID PRIMARY KEY (ID),
CONSTRAINT FK_Events_Meta__Event_ID FOREIGN KEY (Event_ID) REFERENCES dbo.Events (ID)
);
Cela simplifierait vos données pour :
EventID | StartDateTime | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
1 | 2014-01-03 10:00:00 | 604800 | NULL
1 | 2014-01-04 18:00:00 | 604800 | NULL
Il vous permet également d'ajouter une date de fin à votre répétition, c'est-à-dire si vous souhaitez qu'elle ne se répète que pendant une semaine. Votre requête se simplifie alors :
DECLARE @EndDate DATETIME = '20140130';
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT e.ID,
e.Name,
EventDate = DATEADD(SECOND, n.Number * em.IntervalRepeat, em.StartDateTime)
FROM Events e
INNER JOIN Events_Meta em
ON em.Event_ID = e.ID
CROSS JOIN Numbers n
WHERE DATEADD(SECOND, n.Number * em.IntervalRepeat, em.StartDateTime) <= @EndDate
AND ( DATEADD(SECOND, n.Number * em.IntervalRepeat, em.StartDateTime) <= em.RepeatEndDate
OR em.RepeatEndDate IS NULL
)
ORDER BY EventDate;
Exemple sur SQL Fiddle
Je ne vous donnerai pas mon schéma complet de la façon dont j'y suis parvenu dans le passé, mais je donnerai un exemple très simplifié, à partir duquel vous pourrez, espérons-le, construire le vôtre. J'ajouterai seulement un exemple pour un événement qui se produit chaque semaine du lundi au vendredi :
Dans l'ER RepeatEvent ci-dessus stocke les informations de base pour l'événement récurrent, puis selon le type de répétition (quotidien, hebdomadaire, mensuel) une ou plusieurs des autres tables sont remplies. Dans l'exemple d'un événement hebdomadaire, il stockerait tous les jours de la semaine qu'il se répète dans la table RepeatDay
. Si cela devait être limité à certains mois seulement, vous pourriez stocker ces mois dans RepeatMonth
, et ainsi de suite.
Ensuite, en utilisant un tableau de calendrier, vous pouvez obtenir toutes les dates possibles après la première date, et les limiter uniquement aux dates qui correspondent au jour de la semaine/mois de l'année, etc. :
WITH RepeatingEvents AS
( SELECT e.Name,
re.StartDateTime,
re.EndDateTime,
re.TimesToRepeat,
RepeatEventDate = CAST(c.DateKey AS DATETIME) + CAST(re.StartTime AS DATETIME),
RepeatNumber = ROW_NUMBER() OVER(PARTITION BY re.RepeatEventID ORDER BY c.Datekey)
FROM dbo.Event e
INNER JOIN dbo.RepeatEvent re
ON e.EventID = re.EventID
INNER JOIN dbo.RepeatType rt
ON rt.RepeatTypeID = re.RepeatTypeID
INNER JOIN dbo.Calendar c
ON c.DateKey >= re.StartDate
INNER JOIN dbo.RepeatDayOfWeek rdw
ON rdw.RepeatEventID = re.RepeatEventID
AND rdw.DayNumberOfWeek = c.DayNumberOfWeek
WHERE rt.Name = 'Weekly'
)
SELECT Name, StartDateTime, RepeatEventDate, RepeatNumber
FROM RepeatingEvents
WHERE (TimesToRepeat IS NULL OR RepeatNumber <= TimesToRepeat)
AND (EndDateTime IS NULL OR RepeatEventDate <= EndDateTime);
Exemple sur SQL Fiddle
Ceci n'est qu'une représentation très basique de la façon dont je l'ai implémenté, par exemple, j'ai utilisé entièrement des vues pour les données répétées afin que tout événement sans entrée dans RepeatDayOfWeek
serait supposé se répéter tous les jours, plutôt que jamais. Avec tous les autres détails de cette réponse et d'autres, vous devriez, espérons-le, en avoir plus qu'assez pour commencer.