Display next event date

前端 未结 5 641
离开以前
离开以前 2020-12-02 18:13

The UI design for storing event and event meta data is

\"enter

<
相关标签:
5条回答
  • 2020-12-02 18:15

    Your first step is to get your event start dates with each event, and the repeat interval, to do this you can use:

    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));
    

    This gives:

    EventID | Name         | StartDateTime       | RepeatInterval
    --------+--------------+---------------------+-----------------
       1    | Billa Vist   | 2014-01-03 10:00:00 |     604800
       1    | Billa Vist   | 2014-01-04 18:00:00 |     604800
    

    To get this to repeat you will need a numbers table to cross join to, if you don't have one there are a number of ways to generate one on the fly, for simplicity reasons I will use:

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

    For further reading, Aaron Bertrand has done some in depth comparisons ways of generating sequential lists of numbers:

    • Generate a set or sequence without loops – part 1
    • Generate a set or sequence without loops – part 2
    • Generate a set or sequence without loops – part 3

    If we limit our numbers table to only 0 - 5, and only look at the first event, cross joining the two will 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
    

    Then you can get your occurance by adding RepeatInterval * Number to the event start time:

    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;
    

    This gives your expected 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
    

    Example on SQL Fiddle


    I think the schema you have is questionable though, the join on:

    Meta_Key = 'repeat_interval_' + CAST(rs.ID AS VARCHAR(10))
    

    is flimsy at best. I think you would be much better off storing the start date and repeat interval associated with it together:

    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)
    );
    

    This would simplify your data to:

    EventID | StartDateTime       | RepeatInterval | RepeatEndDate
    --------+---------------------+----------------+---------------
       1    | 2014-01-03 10:00:00 |    604800      |     NULL
       1    | 2014-01-04 18:00:00 |    604800      |     NULL
    

    It also allows you to add an end date to your repeat, i.e. if you only want it to repeat for one week. This then your query simlifies to:

    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


    I won't give you my full schema for how I have achieved this in the past, but I will give a very cut down example, from which you can hopefully build your own. I will only add an example for an event that occurs weekly on Mon-Fri:

    enter image description here

    In the above ER RepeatEvent stores the basic information for the recurring event, then depending on the repeat type (Daily, weekly, monthly) one or more of the other tables is populated. In example of a weekly event, it would store all the days of the week that it repeats in in the table RepeatDay. If this needed to be limited to only certain months, you could store these months in RepeatMonth, and so on.

    Then using a calendar table you can get all the possible dates after the first date, and limit these to only those dates that match the day of the week/month of the year etc:

    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

    This is only a very basic representation of how I implemented it, for instance I actually used entirely views any query for the repeating data so that any event with no entries in RepeatDayOfWeek would be assumed to repeat every day, rather than never. Along with all the other detail in this and other answers, you should hopefully have more than enough to get you started.

    0 讨论(0)
  • 2020-12-02 18:22

    This will do it!

    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'
    
    0 讨论(0)
  • 2020-12-02 18:23

    One way of accomplishing this is by using a loop and inserting a record into a temp table. Then you can simply select from your temp table. In the example below I saved off both the unixtimestamp value as well as that value converted to 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
    

    Another possible solution for this problem would to utilize a recursive CTE.

    0 讨论(0)
  • 2020-12-02 18:24

    This code works as desired, all field names match your SQL FIDDLE, output will need tweaked very slightly to show your custom time format. I could not find a default conversion for your format.

    http://www.sqlfiddle.com/#!3/057fe/1

    This solution uses CTE to recursively create new rows of data, each one starting at a point in time and then being an increment of the "Interval" until it meets the end date. Once this cte table is created, it requires a simple query to extract your data.

    ; -- 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
    
    0 讨论(0)
  • 2020-12-02 18:39

    The following will generate events based on StartEvent and MEta description with a CTE.

    Change the values for MaxDate and MaxEvents according to parameters values.

    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 ;
    
    0 讨论(0)
提交回复
热议问题