Dit første skridt er at få din begivenheds startdatoer med hver begivenhed, og gentagelsesintervallet, for at gøre dette kan du bruge:
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));
Dette giver:
EventID | Name | StartDateTime | RepeatInterval
--------+--------------+---------------------+-----------------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800
1 | Billa Vist | 2014-01-04 18:00:00 | 604800
For at få dette til at gentage skal du bruge en taltabel til at krydse join til, hvis du ikke har en, er der en række måder at generere en på i farten, af overskuelighedsgrunde vil jeg bruge:
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT Number
FROM Numbers;
For yderligere læsning har Aaron Bertrand lavet nogle dybdegående sammenligningsmetoder til at generere sekventielle lister over tal:
- Generer et sæt eller en sekvens uden loops – del1
- Generer et sæt eller en sekvens uden loops – del2
- Generer et sæt eller en sekvens uden loops – del3
Hvis vi begrænser vores taltabel til kun 0 - 5 og kun ser på den første begivenhed, vil krydsforbindelse af de to give:
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
Så kan du få din forekomst ved at tilføje RepeatInterval * Number
til begivenhedens starttidspunkt:
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;
Dette giver dit forventede 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
Eksempel på SQL Fiddle
Jeg tror dog, at skemaet du har er tvivlsomt, joinforbindelsen på:
Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
er i bedste fald spinkel. Jeg tror, du ville være meget bedre stillet med at gemme startdatoen og gentagelsesintervallet, der er forbundet med det, sammen:
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)
);
Dette ville forenkle dine data til:
EventID | StartDateTime | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
1 | 2014-01-03 10:00:00 | 604800 | NULL
1 | 2014-01-04 18:00:00 | 604800 | NULL
Det giver dig også mulighed for at tilføje en slutdato til din gentagelse, dvs. hvis du kun ønsker, at den skal gentages i en uge. Dette forenkler din forespørgsel til:
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;
Eksempel på SQL Fiddle
Jeg vil ikke give dig mit fulde skema for, hvordan jeg har opnået dette tidligere, men jeg vil give et meget nedskåret eksempel, som du forhåbentlig kan bygge dit eget ud fra. Jeg vil kun tilføje et eksempel for en begivenhed, der finder sted ugentligt mandag-fredag:
I ovenstående ER gemmer RepeatEvent de grundlæggende oplysninger for den tilbagevendende begivenhed, og afhængigt af gentagelsestypen (Daglig, ugentlig, månedlig) er en eller flere af de andre tabeller udfyldt. I et eksempel på en ugentlig begivenhed vil den gemme alle ugens dage, som den gentages i tabellen RepeatDay
. Hvis dette kun skulle begrænses til bestemte måneder, kunne du gemme disse måneder i RepeatMonth
, og så videre.
Så ved hjælp af en kalendertabel kan du få alle mulige datoer efter den første dato, og begrænse disse til kun de datoer, der matcher ugedagen/måneden i året osv.:
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);
Eksempel på SQL Fiddle
Dette er kun en meget grundlæggende repræsentation af, hvordan jeg implementerede det, for eksempel brugte jeg faktisk fuldstændigt visninger af enhver forespørgsel for de gentagne data, så enhver begivenhed uden indtastninger i RepeatDayOfWeek
ville blive antaget at gentage hver dag, snarere end aldrig. Sammen med alle de andre detaljer i dette og andre svar, skulle du forhåbentlig have mere end nok til at komme i gang.