Datumsbereiche vergleichen


116

Wenn ich in MySQL eine Liste von Datumsbereichen habe (Bereichsstart und Bereichsende). z.B

10/06/1983 to 14/06/1983
15/07/1983 to 16/07/1983
18/07/1983 to 18/07/1983

Und ich möchte überprüfen, ob ein anderer Datumsbereich einen der bereits in der Liste enthaltenen Bereiche enthält. Wie würde ich das tun?

z.B

06/06/1983 to 18/06/1983 = IN LIST
10/06/1983 to 11/06/1983 = IN LIST
14/07/1983 to 14/07/1983 = NOT IN LIST

Antworten:


437

Dies ist ein klassisches Problem, und es ist tatsächlich einfacher, wenn Sie die Logik umkehren.

Lassen Sie mich Ihnen ein Beispiel geben.

Ich werde hier einen Zeitraum und all die verschiedenen Variationen anderer Zeiträume veröffentlichen, die sich in irgendeiner Weise überschneiden.

           |-------------------|          compare to this one
               |---------|                contained within
           |----------|                   contained within, equal start
                   |-----------|          contained within, equal end
           |-------------------|          contained within, equal start+end
     |------------|                       not fully contained, overlaps start
                   |---------------|      not fully contained, overlaps end
     |-------------------------|          overlaps start, bigger
           |-----------------------|      overlaps end, bigger
     |------------------------------|     overlaps entire period

Lassen Sie mich andererseits alle diejenigen posten, die sich nicht überschneiden:

           |-------------------|          compare to this one
     |---|                                ends before
                                 |---|    starts after

Wenn Sie also einfach den Vergleich reduzieren auf:

starts after end
ends before start

Dann finden Sie alle, die sich nicht überlappen, und dann finden Sie alle nicht übereinstimmenden Zeiträume.

In Ihrem letzten NOT IN LIST-Beispiel können Sie sehen, dass es diesen beiden Regeln entspricht.

Sie müssen entscheiden, ob die folgenden Zeiträume IN oder AUSSERHALB Ihrer Bereiche liegen:

           |-------------|
   |-------|                       equal end with start of comparison period
                         |-----|   equal start with end of comparison period

Wenn Ihre Tabelle Spalten mit den Namen range_end und range_start enthält, finden Sie hier einige einfache SQL-Anweisungen zum Abrufen aller übereinstimmenden Zeilen:

SELECT *
FROM periods
WHERE NOT (range_start > @check_period_end
           OR range_end < @check_period_start)

Beachten Sie das NICHT dort. Da die beiden einfachen Regeln alle nicht übereinstimmenden Zeilen finden, wird sie durch ein einfaches NICHT umgekehrt, um zu sagen: Wenn es sich nicht um eine der nicht übereinstimmenden Zeilen handelt, muss es eine der übereinstimmenden sein .

Wenn Sie hier eine einfache Umkehrlogik anwenden, um das NICHT loszuwerden, erhalten Sie:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start

45
Wir benötigen ein "enthält ACII-Diagramme" -Flag für Antworten, mit dem Sie sie mehr als einmal bewerten können
Jonny Buchanan

29
Wahrscheinlich eine der 5 besten Antworten, die ich auf SO gesehen habe. Tolle Erklärung des Problems, nette Anleitung zur Lösung und ... Bilder!
Davidavr

10
Wenn ich dies mehr als einmal abstimmen könnte, würde ich. Tolle, klare und prägnante Erklärung eines häufig auftretenden Problems, dessen Lösung ich selten so gut erklärt gesehen habe!
ConroyP

2
Gute Antwort! Das einzige, was ich hinzufügen möchte - in Bezug auf die Entscheidung, ob Endpunkte enthalten sind oder nicht -, funktioniert alles sauberer, wenn Sie ein geschlossenes Intervall auf der einen Seite und ein offenes Intervall auf der anderen Seite wählen. Zum Beispiel enthält der Anfang eines Bereichs den Punkt und das Ende des Bereichs nicht. Besonders wenn Sie mit einer Kombination aus Datum und Uhrzeit mit verschiedenen Auflösungen arbeiten, wird alles einfacher.
Eclipse

1
Gute Antwort. Dies wird auch als Allens Intervallalgebra bezeichnet . Ich habe eine ähnliche Antwort und geriet in einen heftigen Kampf um die Anzahl der verschiedenen Vergleiche mit einem Kommentator.
Jonathan Leffler

8

Wenn Sie Ihren Beispielbereich vom 06.06.1983 bis zum 18.06.1983 verwenden und davon ausgehen, dass Sie Spalten mit den Namen Start und Ende für Ihre Bereiche haben, können Sie eine Klausel wie diese verwenden

where ('1983-06-06' <= end) and ('1983-06-18' >= start)

Überprüfen Sie also, ob der Beginn Ihres Testbereichs vor dem Ende des Datenbankbereichs liegt und ob das Ende Ihres Testbereichs nach oder am Anfang des Datenbankbereichs liegt.


4

Wenn Ihr RDBMS die Funktion OVERLAP () unterstützt, ist dies trivial - es sind keine selbst entwickelten Lösungen erforderlich. (In Oracle funktioniert es anscheinend, ist aber nicht dokumentiert).


1
Epische Lösung. Funktioniert gut. Dies ist die Syntax für 2 Datumsbereiche (s1, e1) und (s2, e2) in Oracle: Wählen Sie 1 aus dual aus, wobei sich (s1, e1) überschneiden (s2, e2);
Ihebiheb

0

In Ihren erwarteten Ergebnissen sagen Sie

06/06/1983 bis 18/06/1983 = IN LIST

Dieser Zeitraum enthält jedoch keinen der Zeiträume in Ihrer Tabelle (keine Liste!) Von Zeiträumen und ist auch nicht in diesen enthalten. Es überschneidet sich jedoch mit dem Zeitraum 10/06/1983 bis 14/06/1983.

Vielleicht finden Sie das Snodgrass-Buch ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ) nützlich: Es datiert vor MySQL, aber das Konzept der Zeit hat sich nicht geändert ;-)


0

Ich habe eine Funktion erstellt, um dieses Problem in MySQL zu lösen. Konvertieren Sie die Daten einfach vor der Verwendung in Sekunden.

DELIMITER ;;

CREATE FUNCTION overlap_interval(x INT,y INT,a INT,b INT)
RETURNS INTEGER DETERMINISTIC
BEGIN
DECLARE
    overlap_amount INTEGER;
    IF (((x <= a) AND (a < y)) OR ((x < b) AND (b <= y)) OR (a < x AND y < b)) THEN
        IF (x < a) THEN
            IF (y < b) THEN
                SET overlap_amount = y - a;
            ELSE
                SET overlap_amount = b - a;
            END IF;
        ELSE
            IF (y < b) THEN
                SET overlap_amount = y - x;
            ELSE
                SET overlap_amount = b - x;
            END IF;
        END IF;
    ELSE
        SET overlap_amount = 0;
    END IF;
    RETURN overlap_amount;
END ;;

DELIMITER ;

0

Schauen Sie sich das folgende Beispiel an. Es wird für Sie hilfreich sein.

    SELECT  DISTINCT RelatedTo,CAST(NotificationContent as nvarchar(max)) as NotificationContent,
                ID,
                Url,
                NotificationPrefix,
                NotificationDate
                FROM NotificationMaster as nfm
                inner join NotificationSettingsSubscriptionLog as nfl on nfm.NotificationDate between nfl.LastSubscribedDate and isnull(nfl.LastUnSubscribedDate,GETDATE())
  where ID not in(SELECT NotificationID from removednotificationsmaster where Userid=@userid) and  nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo

0

Versuchen Sie dies unter MS SQL


WITH date_range (calc_date) AS (
SELECT DATEADD(DAY, DATEDIFF(DAY, 0, [ending date]) - DATEDIFF(DAY, [start date], [ending date]), 0)
UNION ALL SELECT DATEADD(DAY, 1, calc_date)
FROM date_range 
WHERE DATEADD(DAY, 1, calc_date) <= [ending date])
SELECT  P.[fieldstartdate], P.[fieldenddate]
FROM date_range R JOIN [yourBaseTable] P on Convert(date, R.calc_date) BETWEEN convert(date, P.[fieldstartdate]) and convert(date, P.[fieldenddate]) 
GROUP BY  P.[fieldstartdate],  P.[fieldenddate];

0
CREATE FUNCTION overlap_date(s DATE, e DATE, a DATE, b DATE)
RETURNS BOOLEAN DETERMINISTIC
RETURN s BETWEEN a AND b or e BETWEEN a and b or  a BETWEEN s and e;

0

Eine andere Methode mit der Anweisung BETWEEN sql

Zeiträume enthalten:

SELECT *
FROM periods
WHERE @check_period_start BETWEEN range_start AND range_end
  AND @check_period_end BETWEEN range_start AND range_end

Ausgeschlossene Zeiträume:

SELECT *
FROM periods
WHERE (@check_period_start NOT BETWEEN range_start AND range_end
  OR @check_period_end NOT BETWEEN range_start AND range_end)

-2
SELECT * 
FROM tabla a 
WHERE ( @Fini <= a.dFechaFin AND @Ffin >= a.dFechaIni )
  AND ( (@Fini >= a.dFechaIni AND @Ffin <= a.dFechaFin) OR (@Fini >= a.dFechaIni AND @Ffin >= a.dFechaFin) OR (a.dFechaIni>=@Fini AND a.dFechaFin <=@Ffin) OR
(a.dFechaIni>=@Fini AND a.dFechaFin >=@Ffin) )

Willkommen bei Stack Overflow! Vielen Dank für dieses Code-Snippet, das möglicherweise sofortige Hilfe bietet. Eine richtige Erklärung würde den Bildungswert erheblich verbessern , indem sie zeigt, warum dies eine gute Lösung für das Problem ist, und sie für zukünftige Leser mit ähnlichen, aber nicht identischen Fragen nützlicher machen. Bitte bearbeiten Sie Ihre Antwort, um eine Erklärung hinzuzufügen, und geben Sie an, welche Einschränkungen und Annahmen gelten.
Toby Speight
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.