Pierwszym krokiem jest ustalenie dat rozpoczęcia wydarzeń dla każdego wydarzenia i interwału powtarzania, w tym celu możesz użyć:
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));
Daje to:
EventID | Name | StartDateTime | RepeatInterval
--------+--------------+---------------------+-----------------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800
1 | Billa Vist | 2014-01-04 18:00:00 | 604800
Aby to powtórzyć, będziesz potrzebować tabeli liczb do łączenia krzyżowego, jeśli nie masz takiej, istnieje kilka sposobów na jej wygenerowanie w locie, dla uproszczenia użyję:
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT Number
FROM Numbers;
Aby dowiedzieć się więcej, Aaron Bertrand dokonał kilku dogłębnych porównań sposobów generowania sekwencyjnych list liczb:
- Generuj zestaw lub sekwencję bez pętli – część 1
- Generuj zestaw lub sekwencję bez pętli – część 2
- Generuj zestaw lub sekwencję bez pętli – część 3
Jeśli ograniczymy naszą tabelę liczb tylko do 0 - 5 i spojrzymy tylko na pierwsze zdarzenie, skrzyżowanie tych dwóch da:
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
Następnie możesz uzyskać swoje wystąpienie, dodając RepeatInterval * Number
do czasu rozpoczęcia wydarzenia:
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;
Daje to oczekiwany wynik:
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
Przykład w SQL Fiddle
Myślę, że schemat, który masz, jest wątpliwy, dołączanie:
Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
jest w najlepszym razie wątły. Myślę, że znacznie lepiej byłoby przechowywać razem datę rozpoczęcia i powiązany z nią interwał powtórzeń:
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)
);
To uprościłoby Twoje dane do:
EventID | StartDateTime | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
1 | 2014-01-03 10:00:00 | 604800 | NULL
1 | 2014-01-04 18:00:00 | 604800 | NULL
Pozwala również dodać datę zakończenia do powtórki, tj. jeśli chcesz, aby powtarzała się tylko przez tydzień. To wtedy Twoje zapytanie upraszcza się do:
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;
Przykład w SQL Fiddle
Nie podam pełnego schematu tego, jak udało mi się to osiągnąć w przeszłości, ale podam bardzo okrojony przykład, z którego, miejmy nadzieję, można zbudować własny. Dodam tylko przykład dla wydarzenia, które odbywa się co tydzień od poniedziałku do piątku:
W powyższym ER RepeatEvent przechowuje podstawowe informacje o powtarzającym się zdarzeniu, a następnie w zależności od typu powtórzenia (codziennie, co tydzień, co miesiąc) wypełniana jest jedna lub więcej innych tabel. Na przykład cotygodniowe wydarzenie, przechowuje wszystkie dni tygodnia, w których się powtarza, w tabeli RepeatDay
. Jeśli musiałoby to być ograniczone tylko do określonych miesięcy, możesz przechowywać te miesiące w RepeatMonth
i tak dalej.
Następnie korzystając z tabeli kalendarza, możesz uzyskać wszystkie możliwe daty po pierwszej dacie i ograniczyć je tylko do tych dat, które pasują do dnia tygodnia/miesiąca roku itp.
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);
Przykład w SQL Fiddle
Jest to tylko bardzo podstawowa reprezentacja tego, jak to zaimplementowałem, na przykład faktycznie użyłem całkowicie widoków dowolnego zapytania dla powtarzających się danych, tak aby każde zdarzenie bez wpisów w RepeatDayOfWeek
zakłada się, że będzie się powtarzać codziennie, a nie nigdy. Wraz ze wszystkimi innymi szczegółami w tej i innych odpowiedziach, miejmy nadzieję, że powinieneś mieć wystarczająco dużo, aby zacząć.