Je eerste stap is om de startdatums van je evenement bij elk evenement te krijgen, en het herhalingsinterval, om dit te doen, kun je het volgende gebruiken:
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));
Dit geeft:
EventID | Name | StartDateTime | RepeatInterval
--------+--------------+---------------------+-----------------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800
1 | Billa Vist | 2014-01-04 18:00:00 | 604800
Om dit te laten herhalen heb je een getallentabel nodig om mee te cross-joinen. Als je er geen hebt, zijn er een aantal manieren om er meteen een te genereren, om redenen van eenvoud zal ik gebruiken:
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT Number
FROM Numbers;
Voor meer informatie heeft Aaron Bertrand een aantal diepgaande vergelijkingen gemaakt om opeenvolgende lijsten met getallen te genereren:
- Genereer een set of reeks zonder lussen – deel1
- Genereer een set of reeks zonder loops – part2
- Genereer een set of reeks zonder loops – part3
Als we onze tafel met getallen beperken tot alleen 0 - 5, en alleen naar de eerste gebeurtenis kijken, levert het kruisen van de twee het volgende op:
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
Dan kunt u uw voorkomen krijgen door RepeatInterval * Number
. toe te voegen naar de starttijd van het evenement:
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;
Dit geeft uw verwachte output:
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
Voorbeeld op SQL Fiddle
Ik denk dat het schema dat je hebt twijfelachtig is, de join on:
Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
is op zijn best fragiel. Ik denk dat het veel beter is om de startdatum en het bijbehorende herhalingsinterval samen op te slaan:
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)
);
Dit zou uw gegevens vereenvoudigen tot:
EventID | StartDateTime | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
1 | 2014-01-03 10:00:00 | 604800 | NULL
1 | 2014-01-04 18:00:00 | 604800 | NULL
U kunt ook een einddatum aan uw herhaling toevoegen, d.w.z. als u wilt dat deze slechts één week wordt herhaald. Dit vereenvoudigt uw vraag tot:
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;
Voorbeeld op SQL Fiddle
Ik zal je niet mijn volledige schema geven van hoe ik dit in het verleden heb bereikt, maar ik zal een heel kort voorbeeld geven, waaruit je hopelijk je eigen schema kunt bouwen. Ik zal alleen een voorbeeld toevoegen voor een evenement dat wekelijks plaatsvindt op ma-vr:
In het bovenstaande slaat ER RepeatEvent de basisinformatie op voor het terugkerende evenement, waarna, afhankelijk van het herhalingstype (dagelijks, wekelijks, maandelijks) een of meer van de andere tabellen worden gevuld. In een voorbeeld van een wekelijkse gebeurtenis zou het alle dagen van de week waarin het zich herhaalt opslaan in de tabel RepeatDay
. Als dit beperkt moest worden tot bepaalde maanden, zou u deze maanden kunnen opslaan in RepeatMonth
, enzovoort.
Vervolgens kunt u met behulp van een kalendertabel alle mogelijke datums na de eerste datum krijgen en deze beperken tot alleen die datums die overeenkomen met de dag van de week/maand van het jaar enz.:
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);
Voorbeeld op SQL Fiddle
Dit is slechts een zeer eenvoudige weergave van hoe ik het heb geïmplementeerd, ik heb bijvoorbeeld elke query voor de herhalende gegevens volledig bekeken, zodat elke gebeurtenis zonder vermeldingen in RepeatDayOfWeek
zou worden verondersteld zich elke dag te herhalen, in plaats van nooit. Samen met alle andere details in deze en andere antwoorden, zou je hopelijk meer dan genoeg moeten hebben om je op weg te helpen.