显示下一个活动日期

Bil*_*lla 11 sql sql-server sql-server-2008

用于存储事件和事件元数据的UI设计

在此输入图像描述

SQL TABLE DESIGN

CREATE TABLE [dbo].[EVENTS]
([ID] [int] IDENTITY(1,1) NOT NULL,
 [Name] [nvarchar](255) NOT NULL)
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

事件数据是 事件

事件元数据是 Events_Meta表

我跟着重复日历事件和一些最终的数学,我写了下面的查询

列出所有在结束日期之前的事件日期

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
Run Code Online (Sandbox Code Playgroud)

我没有得到任何东西.我想以给定的间隔显示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访问,2014年1月3日上午10点

Billa Visit,2014年1月4日上午10点

Billa访问,2014年1月10日上午10点

Billa Visit,2014年1月11日上午10点

Billa访问,2014年1月17日上午10点

Billa Visit,2014年1月18日上午10点

Billa Visit,2014年1月24日上午10点

Billa Visit,2014年1月25日上午10点

SQL FIDDLE LINK

Gar*_*thD 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));
Run Code Online (Sandbox Code Playgroud)

这给出了:

EventID | Name         | StartDateTime       | RepeatInterval
--------+--------------+---------------------+-----------------
   1    | Billa Vist   | 2014-01-03 10:00:00 |     604800
   1    | Billa Vist   | 2014-01-04 18:00:00 |     604800
Run Code Online (Sandbox Code Playgroud)

要重复此操作,您需要一个数字表来交叉连接,如果您没有,有很多方法可以动态生成一个,为简单起见,我将使用:

WITH Numbers AS
(   SELECT  Number = ROW_NUMBER() OVER(ORDER BY a.object_id) - 1
    FROM    sys.all_objects a
)
SELECT  Number
FROM    Numbers;
Run Code Online (Sandbox Code Playgroud)

为了进一步阅读,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
Run Code Online (Sandbox Code Playgroud)

然后,您可以通过添加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;
Run Code Online (Sandbox Code Playgroud)

这给出了您的预期输出:

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
Run Code Online (Sandbox Code Playgroud)

关于SQL小提琴的例子


我认为你的架构是有问题的,加入:

Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
Run Code Online (Sandbox Code Playgroud)

充其量是脆弱的.我认为你最好将开始日期和重复间隔存储在一起:

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)
);
Run Code Online (Sandbox Code Playgroud)

这会将您的数据简化为:

EventID | StartDateTime       | RepeatInterval | RepeatEndDate
--------+---------------------+----------------+---------------
   1    | 2014-01-03 10:00:00 |    604800      |     NULL
   1    | 2014-01-04 18:00:00 |    604800      |     NULL
Run Code Online (Sandbox Code Playgroud)

它还允许您为重复添加结束日期,即如果您只希望重复一周.然后,您的查询将简化为:

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;
Run Code Online (Sandbox Code Playgroud)

关于SQL小提琴的例子


我不会向你提供我过去如何实现这一目标的完整模式,但我将给出一个非常简洁的示例,您可以从中建立自己的模型.我只会为周一至周五每周发生的事件添加一个示例:

在此输入图像描述

在上面的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);
Run Code Online (Sandbox Code Playgroud)

关于SQL小提琴的例子

这只是我如何实现它的一个非常基本的表示,例如我实际上使用完全查看重复数据的任何查询,以便任何没有条目的事件RepeatDayOfWeek将被假定为每天重复,而不是从不重复.除了这个和其他答案中的所有其他细节,你应该有足够的东西让你开始.

  • 总而言之,我认为没有正确或错误的方法,这一切都取决于你需要什么和你感到满意,我怀疑SO上的任何人都会为你设计你的系统,最终它是问答网站不是咨询服务.我很乐意提供更多帮助,但如果我发布了我随附文档使用的架构,这将是一个巨大的答案,我必须写,因为它不存在,它也需要我几个小时完整的文件,这对于免费工作来说有点太长了! (2认同)