Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Datum des nächsten Ereignisses anzeigen

Ihr erster Schritt besteht darin, Ihre Veranstaltungsstartdaten für jede Veranstaltung und das Wiederholungsintervall abzurufen. Dazu können Sie Folgendes verwenden:

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

Das ergibt:

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

Um dies zu wiederholen, benötigen Sie eine Zahlentabelle, mit der Sie sich verbinden können. Wenn Sie keine haben, gibt es eine Reihe von Möglichkeiten, eine im laufenden Betrieb zu erstellen. Der Einfachheit halber werde ich Folgendes verwenden:

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

Zur weiteren Lektüre hat Aaron Bertrand einige eingehende Vergleiche durchgeführt, um fortlaufende Zahlenlisten zu erstellen:

  • Erzeuge ein Set oder eine Sequenz ohne Schleifen – Teil1
  • Erzeuge ein Set oder eine Sequenz ohne Schleifen – Teil2
  • Erzeuge ein Set oder eine Sequenz ohne Schleifen – Teil 3

Wenn wir unsere Zahlentabelle auf nur 0 - 5 beschränken und nur das erste Ereignis betrachten, ergibt die Kreuzverbindung der beiden:

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

Dann können Sie Ihr Auftreten erhalten, indem Sie RepeatInterval * Number hinzufügen bis zur Startzeit des Events:

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;

Dies ergibt Ihre erwartete Ausgabe:

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

Beispiel für SQL Fiddle

Ich denke jedoch, dass das Schema, das Sie haben, fragwürdig ist, der Join auf:

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

ist bestenfalls dünn. Ich denke, Sie sollten das Startdatum und das damit verbundene Wiederholungsintervall viel besser zusammen speichern:

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

Dies würde Ihre Daten vereinfachen zu:

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

Außerdem können Sie Ihrer Wiederholung ein Enddatum hinzufügen, z. B. wenn Sie möchten, dass sie sich nur eine Woche lang wiederholt. Dies vereinfacht dann Ihre Abfrage zu:

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;

Beispiel für SQL Fiddle

Ich werde Ihnen nicht mein vollständiges Schema dafür geben, wie ich dies in der Vergangenheit erreicht habe, aber ich werde ein sehr reduziertes Beispiel geben, aus dem Sie hoffentlich Ihr eigenes bauen können. Ich werde nur ein Beispiel für ein Ereignis hinzufügen, das wöchentlich von Mo-Fr stattfindet:

Im obigen ER speichert RepeatEvent die grundlegenden Informationen für das wiederkehrende Ereignis, dann werden je nach Wiederholungstyp (täglich, wöchentlich, monatlich) eine oder mehrere der anderen Tabellen gefüllt. Im Beispiel eines wöchentlichen Ereignisses würde es alle Wochentage, an denen es sich wiederholt, in der Tabelle RepeatDay speichern . Wenn dies auf bestimmte Monate beschränkt werden soll, können Sie diese Monate in RepeatMonth speichern , und so weiter.

Dann können Sie mit einer Kalendertabelle alle möglichen Daten nach dem ersten Datum erhalten und diese auf nur die Daten beschränken, die mit dem Wochentag/Monat des Jahres usw. übereinstimmen:

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

Beispiel für SQL Fiddle

Dies ist nur eine sehr grundlegende Darstellung dessen, wie ich es implementiert habe, zum Beispiel habe ich tatsächlich alle Abfragen für die sich wiederholenden Daten vollständig verwendet, sodass jedes Ereignis keine Einträge in RepeatDayOfWeek enthält würde sich eher jeden Tag als nie wiederholen. Zusammen mit all den anderen Details in dieser und anderen Antworten sollten Sie hoffentlich mehr als genug haben, um loszulegen.