Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Afficher la date du prochain événement

Votre première étape consiste à obtenir les dates de début de votre événement avec chaque événement et l'intervalle de répétition. Pour ce faire, vous pouvez utiliser :

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

Cela donne :

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

Pour que cela se répète, vous aurez besoin d'une table de nombres pour effectuer une jointure croisée, si vous n'en avez pas, il existe plusieurs façons d'en générer une à la volée, pour des raisons de simplicité, j'utiliserai :

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

Pour en savoir plus, Aaron Bertrand a effectué des comparaisons approfondies des manières de générer des listes séquentielles de nombres :

  • Générer un ensemble ou une séquence sans boucles – partie 1
  • Générer un ensemble ou une séquence sans boucles – partie 2
  • Générer un ensemble ou une séquence sans boucles – partie 3

Si nous limitons notre table de nombres à seulement 0 - 5, et ne regardons que le premier événement, la jonction croisée des deux donnera :

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

Ensuite, vous pouvez obtenir votre occurrence en ajoutant RepeatInterval * Number à l'heure de début de l'événement :

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;

Cela donne la sortie attendue :

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

Exemple sur SQL Fiddle

Je pense que le schéma que vous avez est discutable cependant, la jointure sur :

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

est au mieux fragile. Je pense que vous feriez bien mieux de stocker ensemble la date de début et l'intervalle de répétition qui lui sont associés :

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

Cela simplifierait vos données pour :

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

Il vous permet également d'ajouter une date de fin à votre répétition, c'est-à-dire si vous souhaitez qu'elle ne se répète que pendant une semaine. Votre requête se simplifie alors :

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;

Exemple sur SQL Fiddle

Je ne vous donnerai pas mon schéma complet de la façon dont j'y suis parvenu dans le passé, mais je donnerai un exemple très simplifié, à partir duquel vous pourrez, espérons-le, construire le vôtre. J'ajouterai seulement un exemple pour un événement qui se produit chaque semaine du lundi au vendredi :

Dans l'ER RepeatEvent ci-dessus stocke les informations de base pour l'événement récurrent, puis selon le type de répétition (quotidien, hebdomadaire, mensuel) une ou plusieurs des autres tables sont remplies. Dans l'exemple d'un événement hebdomadaire, il stockerait tous les jours de la semaine qu'il se répète dans la table RepeatDay . Si cela devait être limité à certains mois seulement, vous pourriez stocker ces mois dans RepeatMonth , et ainsi de suite.

Ensuite, en utilisant un tableau de calendrier, vous pouvez obtenir toutes les dates possibles après la première date, et les limiter uniquement aux dates qui correspondent au jour de la semaine/mois de l'année, 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);

Exemple sur SQL Fiddle

Ceci n'est qu'une représentation très basique de la façon dont je l'ai implémenté, par exemple, j'ai utilisé entièrement des vues pour les données répétées afin que tout événement sans entrée dans RepeatDayOfWeek serait supposé se répéter tous les jours, plutôt que jamais. Avec tous les autres détails de cette réponse et d'autres, vous devriez, espérons-le, en avoir plus qu'assez pour commencer.