Bước đầu tiên của bạn là lấy ngày bắt đầu sự kiện của bạn với mỗi sự kiện và khoảng thời gian lặp lại, để làm điều này, bạn có thể sử dụng:
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));
Điều này mang lại:
EventID | Name | StartDateTime | RepeatInterval
--------+--------------+---------------------+-----------------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800
1 | Billa Vist | 2014-01-04 18:00:00 | 604800
Để lặp lại điều này, bạn sẽ cần một bảng số để kết hợp chéo với nhau, nếu bạn không có bảng đó, có một số cách để tạo một bảng ngay lập tức, vì lý do đơn giản, tôi sẽ sử dụng:
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT Number
FROM Numbers;
Để đọc thêm, Aaron Bertrand đã thực hiện một số so sánh chuyên sâu về cách tạo danh sách số liên tiếp:
- Tạo một tập hợp hoặc chuỗi không có vòng lặp - part1
- Tạo một tập hợp hoặc chuỗi không có vòng lặp - part2
- Tạo một tập hợp hoặc trình tự không có vòng lặp - phần 3
Nếu chúng tôi giới hạn bảng số của mình chỉ từ 0 - 5 và chỉ xem xét sự kiện đầu tiên, kết hợp chéo cả hai sẽ cho:
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
Sau đó, bạn có thể nhận được sự xuất hiện của mình bằng cách thêm RepeatInterval * Number
đến thời gian bắt đầu sự kiện:
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;
Điều này mang lại kết quả mong đợi của bạn:
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
Ví dụ trên SQL Fiddle
Tôi nghĩ rằng lược đồ bạn có vẫn còn nghi vấn, hãy tham gia vào:
Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
tốt nhất là mỏng manh. Tôi nghĩ rằng bạn sẽ tốt hơn nhiều nếu lưu trữ ngày bắt đầu và khoảng thời gian lặp lại được kết hợp với nó cùng nhau:
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)
);
Điều này sẽ đơn giản hóa dữ liệu của bạn thành:
EventID | StartDateTime | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
1 | 2014-01-03 10:00:00 | 604800 | NULL
1 | 2014-01-04 18:00:00 | 604800 | NULL
Nó cũng cho phép bạn thêm ngày kết thúc để lặp lại, tức là nếu bạn chỉ muốn nó lặp lại trong một tuần. Sau đó, truy vấn của bạn sẽ mô phỏng thành:
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;
Ví dụ trên SQL Fiddle
Tôi sẽ không cung cấp cho bạn lược đồ đầy đủ của tôi về cách tôi đã đạt được điều này trong quá khứ, nhưng tôi sẽ đưa ra một ví dụ rất chi tiết, từ đó bạn có thể hy vọng xây dựng cho riêng mình. Tôi sẽ chỉ thêm một ví dụ cho một sự kiện diễn ra hàng tuần vào Thứ Hai-Thứ Sáu:
Trong ER RepeatEvent ở trên lưu trữ thông tin cơ bản cho sự kiện lặp lại, sau đó tùy thuộc vào loại lặp lại (Hàng ngày, hàng tuần, hàng tháng) mà một hoặc nhiều bảng khác được điền. Ví dụ về một sự kiện hàng tuần, nó sẽ lưu trữ tất cả các ngày trong tuần mà nó lặp lại trong bảng RepeatDay
. Nếu điều này chỉ cần được giới hạn trong những tháng nhất định, bạn có thể lưu trữ những tháng này trong RepeatMonth
, và như vậy.
Sau đó, sử dụng bảng lịch, bạn có thể nhận được tất cả các ngày có thể có sau ngày đầu tiên và chỉ giới hạn những ngày này phù hợp với ngày trong tuần / tháng trong năm, v.v.:
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);
Ví dụ trên SQL Fiddle
Đây chỉ là phần trình bày rất cơ bản về cách tôi triển khai nó, chẳng hạn như tôi thực sự đã sử dụng hoàn toàn xem mọi truy vấn cho dữ liệu lặp lại để mọi sự kiện không có mục nhập nào trong RepeatDayOfWeek
sẽ được cho là lặp lại hàng ngày, thay vì không bao giờ. Cùng với tất cả các chi tiết khác trong câu trả lời này và các câu trả lời khác, hy vọng bạn sẽ có nhiều hơn đủ để bắt đầu.