Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Visualizza la data dell'evento successivo

Il primo passo è ottenere le date di inizio dell'evento con ogni evento e l'intervallo di ripetizione, per farlo puoi utilizzare:

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

Questo dà:

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

Per fare in modo che si ripeta avrai bisogno di una tabella di numeri a cui unire in modo incrociato, se non ne hai una ci sono diversi modi per generarne una al volo, per motivi di semplicità userò:

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

Per ulteriori letture, Aaron Bertrand ha effettuato alcuni confronti approfonditi sui modi per generare elenchi sequenziali di numeri:

  • Genera un set o una sequenza senza loop – parte 1
  • Genera un set o una sequenza senza loop – parte 2
  • Genera un set o una sequenza senza loop – parte 3

Se limitiamo la nostra tabella dei numeri a solo 0 - 5 e guardiamo solo al primo evento, l'unione incrociata dei due darà:

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

Quindi puoi ottenere la tua presenza aggiungendo RepeatInterval * Number all'ora di inizio dell'evento:

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;

Questo fornisce l'output previsto:

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

Esempio su SQL Fiddle

Penso che lo schema che hai sia discutibile, il join su:

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

nella migliore delle ipotesi è fragile. Penso che faresti molto meglio a memorizzare insieme la data di inizio e l'intervallo di ripetizione ad essa associato:

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

Ciò semplificherebbe i tuoi dati in:

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

Ti permette anche di aggiungere una data di fine alla tua ripetizione, ad esempio se vuoi che si ripeta solo per una settimana. Questo quindi la tua query semplifica a:

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;

Esempio su SQL Fiddle

Non ti darò il mio schema completo di come ho raggiunto questo obiettivo in passato, ma fornirò un esempio molto ridotto, dal quale si spera che tu possa costruirne uno tuo. Aggiungerò solo un esempio per un evento che si verifica settimanalmente dal lunedì al venerdì:

In ER RepeatEvent sopra vengono memorizzate le informazioni di base per l'evento ricorrente, quindi a seconda del tipo di ripetizione (giornaliera, settimanale, mensile) viene popolata una o più delle altre tabelle. Ad esempio di un evento settimanale, memorizzerebbe tutti i giorni della settimana in cui si ripete nella tabella RepeatDay . Se questo dovesse essere limitato solo a determinati mesi, potresti archiviare questi mesi in RepeatMonth , e così via.

Quindi, utilizzando una tabella del calendario puoi ottenere tutte le date possibili dopo la prima data e limitarle solo a quelle date che corrispondono al giorno della settimana/mese dell'anno ecc.:

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

Esempio su SQL Fiddle

Questa è solo una rappresentazione molto semplice di come l'ho implementato, ad esempio ho effettivamente utilizzato visualizza interamente qualsiasi query per i dati ripetuti in modo che qualsiasi evento senza voci in RepeatDayOfWeek si presume che si ripeta ogni giorno, piuttosto che mai. Insieme a tutti gli altri dettagli in questa e in altre risposte, dovresti avere più che abbastanza per iniziare.