用于存储事件和事件元数据的UI设计是
SQL TABLE DESIGN 是
CREATE TABLE [dbo].[EVENTS]
([ID] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](255) NOT NULL)
和
CREATE TABLE [dbo].[EVENTS_META](
[ID] [int] IDENTITY(1,1) NOT NULL,
[event_id] [int] NOT NULL,
[meta_key] [varchar](255) NOT NULL,
[meta_value] [bigint] NOT NULL)
活动数据是
事件元数据为
我关注Repeating calendar events and some final maths并撰写了以下查询
列出所有在结束日期前的所有日期
SELECT EV.*
FROM events AS EV
RIGHT JOIN events_meta AS EM1 ON EM1.event_id = EV.id
RIGHT JOIN events_meta AS EM2 ON EM2.meta_key = 'repeat_interval_'+ CAST(EM1.id as Varchar(100))
WHERE EM1.meta_key = 'repeat_start'
AND ((1391040000 - EM1.meta_value ) % EM2.meta_value) = 0
我什么都没得到。我想以给定的间隔显示repeat_start之后的所有日期。
这里的例子第一场比赛开始于(2014年1月3日,上午10点)unixtimestamp = 1388743200并且每周五(7天)继续,我们还计划在星期六(2014年1月4日)1388858400开始的第一场比赛,并且每7场比赛继续一次天(星期六)
它可以是一个月/每天/等等。所以我们将interval
定义为秒。
如果我提供一些意见,例如2014年1月30日,i.e =1391040000
(2014年1月30日00:00:00)
预期结果
Billa Visit,2014年1月3日10 A.M
Billa Visit,2014年1月4日10 A.M
Billa Visit,2014年1月10日10 A.M
Billa Visit,2014年1月11日10 A.M
Billa Visit,2014年1月17日10 A.M
Billa Visit,2014年1月18日10 A.M
Billa Visit,2014年1月24日10 A.M
Billa Visit,2014年1月25日10 A.M
答案 0 :(得分:14)
您的第一步是获取每个事件的事件开始日期和重复间隔,以便您可以使用:
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));
这给出了:
EventID | Name | StartDateTime | RepeatInterval
--------+--------------+---------------------+-----------------
1 | Billa Vist | 2014-01-03 10:00:00 | 604800
1 | Billa Vist | 2014-01-04 18:00:00 | 604800
为了重复这一点,你需要一个数字表来交叉加入,如果你没有,有很多方法可以动态生成一个,为了简单起见,我将使用:
WITH Numbers AS
( SELECT Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
FROM sys.all_objects a
)
SELECT Number
FROM Numbers;
为了进一步阅读,Aaron Bertrand已经做了一些深入的比较,以生成连续的数字列表:
如果我们将我们的数字表限制为仅0 - 5,并且仅查看第一个事件,则交叉加入两个将给出:
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
然后,您可以通过将RepeatInterval * Number
添加到活动开始时间来获得您的出现:
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;
这给出了您的预期输出:
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
<强> Example on SQL Fiddle 强>
我认为你所拥有的架构是有问题的,加入:
Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
充其量是脆弱的。我认为你最好存储与它相关的开始日期和重复间隔:
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)
);
这会将您的数据简化为:
EventID | StartDateTime | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
1 | 2014-01-03 10:00:00 | 604800 | NULL
1 | 2014-01-04 18:00:00 | 604800 | NULL
它还允许您为重复添加结束日期,即如果您只希望重复一周。然后,您的查询将简化为:
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;
<强> Example on SQL Fiddle 强>
我不会向你提供我过去如何实现这一目标的完整架构,但我会给出一个非常简洁的示例,您可以从中建立自己的示例。我只会为周一至周五发生的事件添加一个示例:
在上面的ER RepeatEvent中存储了周期性事件的基本信息,然后根据重复类型(每日,每周,每月)填充一个或多个其他表。在每周事件的示例中,它将在表RepeatDay
中存储它重复的一周中的所有日期。如果仅限于某些月份,您可以将这些月份存储在RepeatMonth
中,依此类推。
然后使用日历表,您可以获得第一个日期之后的所有可能日期,并将这些日期限制为仅匹配一年中某一天/哪一天的日期等:
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);
<强> Example on SQL Fiddle 强>
这只是我如何实现它的一个非常基本的表示,例如我实际上使用完全查看重复数据的任何查询,以便假定RepeatDayOfWeek
中没有条目的任何事件每天重复,而不是永远。除了这个和其他答案中的所有其他细节,你应该有足够的东西让你开始。
答案 1 :(得分:3)
以下内容将根据具有CTE的StartEvent和MEta描述生成事件。
根据参数值更改MaxDate和MaxEvents的值。
declare @MaxDate datetime = convert(datetime,'12/2/2014', 101);
declare @MaxEvents integer= 200;
; With
-- number generator by power of 2
n2(n) as ( select 1 as n union all select 1),
n4(n) as ( select 1 from n2 t1 cross join n2 t2 ),
n16(n) as ( select 1 from n4 t1 cross join n4 t2 ),
n256(n) as ( select 1 from n16 t1 cross join n16 t2 ),
n65k(n) as ( select 1 from n256 t1 cross join n256 t2 ),
Numbers (n) as (select row_number() over( order by n) from n65k ),
-- Start of events
StartEvents as
( SELECT 1 as EventNo, EV.Name, EM.ID, EM.Event_Id, EM.Meta_key, dateAdd(second,EM.meta_value,convert(datetime,'01/01/1970', 101)) as EventDate
FROM events AS EV
INNER JOIN events_meta EM
ON EM.event_id = EV.id
AND EM.meta_key = 'repeat_start'),
-- Repeating events N times
NextEvents AS
( SELECT Numbers.N+1 asEventNo, StartEvents.Name, EM.ID, EM.Event_Id, EM.Meta_key, dateAdd(second,EM.meta_value*Numbers.n,StartEvents.EventDate) as EventDate
FROM StartEvents
INNER JOIN events_meta EM
ON EM.event_id = StartEvents.event_id
AND EM.meta_key = 'repeat_interval_'+ ltrim(rtrim(str(StartEvents.ID )))
AND ((1391040000- EM.meta_value ) % EM.meta_value) = 0
cross join Numbers
-- not to overflow (dateadd parameter is int type)
Where Numbers.N < 3000
)
-- startEvents union nextEvents
select EventNo, Name, Meta_key, EventDate
FROM (
Select * from StartEvents
union all select * from NextEvents ) AllEvents
where EventDate < @MaxDate
and EventNo < @MaxEvents
order by ID ;
答案 2 :(得分:1)
实现此目的的一种方法是使用循环并将记录插入临时表。然后,您只需从临时表中进行选择即可。在下面的示例中,我保存了unixtimestamp值以及转换为datetime的值。
declare @enddate bigint, @intervalFactor int, @rowresult int
set @enddate = 1391040000
create table #Results
(
eventid int,
eventdate bigint,
eventdatedate datetime
)
set @rowresult = 1
set @intervalFactor = 0
WHILE (@rowresult > 0)
BEGIN
Insert #Results (eventid, eventdate, eventdatedate)
Select events.id, date.meta_value + (intrvl.meta_value * @intervalFactor)
,DATEADD(ss,date.meta_value + (intrvl.meta_value * @intervalFactor), CAST('1970-01-01 00:00:00' AS datetime))
from events
inner join events_meta date
on events.id = date.event_id
AND date.meta_key = 'repeat_start'
inner join events_meta intrvl
on events.id = intrvl.event_id
and intrvl.meta_key = 'repeat_interval_'+ CAST(date.id as Varchar(100))
where ((@enddate - date.meta_value ) % intrvl.meta_value) >= 0
and date.meta_value + (intrvl.meta_value * @intervalFactor) <= @enddate
set @rowresult = @@rowcount
set @intervalFactor = @intervalFactor + 1
END
select * from #Results
此问题的另一种可能解决方案是使用recursive CTE。
答案 3 :(得分:1)
这样就可以了!
WITH mycte AS
(
SELECT A.ID, A.Name, A.StartDate, A.StartDate AS [IntervalDate], A.Interval, A.[repeat_startID], A.[repeat_intervalID]
FROM (
-- this is your provided query
-- can you run this derived table only and make sure it return what you expect?
SELECT
EV.*
,EM1.id AS [repeat_startID]
,EM2.id AS [repeat_intervalID]
-- I convert to datetime becuase Im more familiar with datatype=time manipulations
,DATEADD(SECOND,EM1.meta_value,'1970-01-01') AS [StartDate]
,EM2.meta_value AS [Interval]
FROM [EVENTS] AS EV
-- I used inner joins, please modify that as needed.
INNER JOIN [EVENTS_META] AS EM1 ON EM1.meta_key = 'repeat_start' AND EM1.event_id = EV.id
INNER JOIN [EVENTS_META] AS EM2 ON EM2.meta_key = 'repeat_interval_'+ CAST(EM1.id as Varchar(100))
) AS A
UNION ALL
SELECT ID, Name, StartDate, DATEADD(SECOND,Interval,[IntervalDate]), Interval, [repeat_startID], [repeat_intervalID]
FROM mycte
WHERE DATEADD(SECOND,1,[IntervalDate]) < '2014-01-30 00:00:00.000' -- this is your epoch timestamp
)
SELECT * FROM mycte
-- it is unclear if the "cutoff" date is for the Last Interval's Start Date or the next one
-- examining the results shows there are 2 records after your"cutoff" date
-- add a WHERE statement to fix this if needed?
-- WHERE [IntervalDate] < '2014-01-30 00:00:00.000' -- this is your epoch timestamp
ORDER BY [repeat_startID], StartDate;
-- produces: (Column #4 is what you are interested in)
1 Billy Visit 2014-01-03 10:00:00.000 2014-01-03 10:00:00.000 604800 1 2
1 Billy Visit 2014-01-03 10:00:00.000 2014-01-10 10:00:00.000 604800 1 2
1 Billy Visit 2014-01-03 10:00:00.000 2014-01-17 10:00:00.000 604800 1 2
1 Billy Visit 2014-01-03 10:00:00.000 2014-01-24 10:00:00.000 604800 1 2
1 Billy Visit 2014-01-03 10:00:00.000 2014-01-31 10:00:00.000 604800 1 2 -- occurs after '2014-01-30 00:00:00.000'
1 Billy Visit 2014-01-04 18:00:00.000 2014-01-04 18:00:00.000 604800 3 4
1 Billy Visit 2014-01-04 18:00:00.000 2014-01-11 18:00:00.000 604800 3 4
1 Billy Visit 2014-01-04 18:00:00.000 2014-01-18 18:00:00.000 604800 3 4
1 Billy Visit 2014-01-04 18:00:00.000 2014-01-25 18:00:00.000 604800 3 4
1 Billy Visit 2014-01-04 18:00:00.000 2014-02-01 18:00:00.000 604800 3 4 -- occurs after '2014-01-30 00:00:00.000'
答案 4 :(得分:0)
此代码可以正常工作,所有字段名称都与您的SQL FIDDLE相匹配,输出将需要稍微调整以显示您的自定义时间格式。我找不到您的格式的默认转换。
http://www.sqlfiddle.com/#!3/057fe/1
此解决方案使用CTE递归创建新的数据行,每行数据从一个时间点开始,然后是“间隔”的增量,直到它达到结束日期。创建此cte表后,需要一个简单的查询来提取数据。
; -- previous statement must terminate w/ semicolon
WITH mycte AS
(
SELECT A.ID, A.Name, A.StartDate, A.StartDate AS [IntervalDate], A.[Interval]
FROM (
-- this is your provided query
SELECT EV.*
-- I added two derived fields to simplify the recursion process.
,DATEADD(SECOND,EM1.meta_value,'1970-01-01') AS StartDate
,EM2.meta_value AS [Interval]
FROM events AS EV
RIGHT JOIN events_meta AS EM1 ON EM1.event_id = EV.id
RIGHT JOIN events_meta AS EM2 ON EM2.meta_key = 'repeat_interval_'+ CAST(EM1.id as Varchar(100))
WHERE EM1.meta_key = 'repeat_start'
) AS A
UNION ALL
SELECT ID, Name, StartDate, DATEADD(SECOND,Interval,[IntervalDate]), [Interval]
FROM mycte
WHERE DATEADD(SECOND,1,[IntervalDate]) < '2014-01-30 00:00:00.000' -- this is your epoch timestamp
)
SELECT Name + ', ' + CONVERT(VARCHAR,[IntervalDate],113)-- format your custom date as needed.
FROM mycte
WHERE [IntervalDate] < '2014-01-30 00:00:00.000' -- this is your epoch timestamp
ORDER BY StartDate
OPTION (Maxrecursion 1000); -- default is 100 if not stated