Porównanie zakresów dat

116

W MySQL, jeśli mam listę zakresów dat (początek zakresu i koniec zakresu). na przykład

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

Chcę sprawdzić, czy inny zakres dat zawiera JAKIEKOLWIEK zakresy znajdujące się już na liście, jak mam to zrobić?

na przykład

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
Kieran Benton
źródło
1
możliwy duplikat Ustalenia, czy dwa zakresy dat pokrywają się
Salman A

Odpowiedzi:

439

To klasyczny problem i jest łatwiejszy, jeśli odwrócisz logikę.

Dam ci przykład.

Opublikuję tutaj jeden okres i wszystkie różne odmiany innych okresów, które w jakiś sposób się pokrywają.

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

z drugiej strony pozwól mi opublikować wszystkie te, które się nie pokrywają:

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

Więc jeśli po prostu zredukujesz porównanie do:

starts after end
ends before start

wtedy znajdziesz wszystkie te, które się nie pokrywają, a potem znajdziesz wszystkie niepasujące okresy.

W ostatnim przykładzie NIE NA LIŚCIE widać, że pasuje do tych dwóch reguł.

Będziesz musiał zdecydować, czy następujące okresy znajdują się w twoich zakresach, czy poza nimi:

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

Jeśli Twoja tabela zawiera kolumny o nazwach range_end i range_start, oto kilka prostych instrukcji SQL do pobrania wszystkich pasujących wierszy:

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

Zwróć uwagę na NIE w tym miejscu. Ponieważ dwie proste reguły znajdują wszystkie niepasujące wiersze, proste NOT odwróci to, mówiąc: jeśli nie jest to jeden z niepasujących wierszy, musi to być jeden z pasujących wierszy .

Stosując tutaj prostą logikę odwrócenia, aby pozbyć się NOT, a otrzymasz:

SELECT *
FROM periods
WHERE range_start <= @check_period_end
      AND range_end >= @check_period_start
Lasse V. Karlsen
źródło
45
Potrzebujemy flagi „zawiera diagramy ACII” dla odpowiedzi, która pozwoli ci głosować za nimi więcej niż jeden raz
Jonny Buchanan
29
Prawdopodobnie jedna z 5 najlepszych odpowiedzi, jakie widziałem w SO. Świetne wyjaśnienie problemu, przyjemny opis rozwiązania i ... zdjęcia!
davidavr
10
Gdybym mógł głosować za tym więcej niż raz, zrobiłbym to. Świetne, jasne i zwięzłe wyjaśnienie często pojawiającego się problemu, rozwiązanie, którego rzadko widziałem tak dobrze wyjaśnione!
ConroyP,
2
Świetna odpowiedź! Jedyne, co chciałbym dodać - w odniesieniu do decydowania, czy punkty końcowe są uwzględnione, czy nie - wszystko działa lepiej, jeśli przejdziesz z interwałem zamkniętym po jednej stronie i interwałem otwartym po drugiej. Np. Początek zakresu zawiera się w punkcie, a koniec zakresu nie. Zwłaszcza gdy masz do czynienia z kombinacją dat i godzin różnych rozdzielczości, wszystko staje się prostsze.
Eclipse
1
Dobra odpowiedź. Jest to również określane jako algebra przedziałów Allena . Mam podobną odpowiedź i toczyłem zaciekłą walkę o to, ile różnych porównań jest z jednym komentatorem.
Jonathan Leffler
8

Biorąc przykładowy zakres od 06.06.1983 do 18.06.1983 i zakładając, że masz kolumny o nazwie początek i koniec dla swoich zakresów, możesz użyć takiej klauzuli

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

tj. sprawdź, czy początek zakresu testowego przypada przed końcem zakresu bazy danych i czy koniec zakresu testowego przypada po lub na początku zakresu bazy danych.

Paul Dixon
źródło
4

Jeśli Twój RDBMS obsługuje funkcję OVERLAP (), staje się to trywialne - nie ma potrzeby tworzenia własnych rozwiązań. (W Oracle najwyraźniej działa, ale nie jest udokumentowane).

David Aldridge
źródło
1
Epickie rozwiązanie. Działa w porządku. Oto składnia dla 2 zakresów dat (s1, e1) i (s2, e2) w Oracle: wybierz 1 z dual, gdzie (s1, e1) nakładają się (s2, e2);
ihebiheb
0

Mówisz o swoich oczekiwanych wynikach

06.06.1983 do 18.06.1983 = NA LIŚCIE

Jednak okres ten nie zawiera ani nie jest zawarty w żadnym z okresów w Twojej tabeli (nie na liście!) Okresów. Nakłada się on jednak na okres od 10.06.1983 do 14.06.1983.

Przydatna może się okazać książka Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ): poprzedza ona mysql, ale pojęcie czasu się nie zmieniło ;-)

onedaywhen
źródło
0

Stworzyłem funkcję do rozwiązania tego problemu w MySQL. Po prostu przekonwertuj daty na sekundy przed użyciem.

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 ;
jonavon
źródło
0

Spójrz na poniższy przykład. Będzie ci to pomocne.

    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
Rama Subba Reddy M
źródło
0

Wypróbuj to na 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];
RickyS
źródło
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;
Paul Williamson
źródło
0

Inna metoda wykorzystująca instrukcję BETWEEN sql

Okresy obejmują:

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

Wyłączone okresy:

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)
Florian HENRY - doradztwo ATM
źródło
-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) )
Gio
źródło
Witamy w Stack Overflow! Dziękujemy za ten fragment kodu, który może zapewnić natychmiastową pomoc. Właściwe wyjaśnienie znacznie poprawiłoby jego wartość edukacyjną, pokazując, dlaczego jest to dobre rozwiązanie problemu, i uczyniłoby go bardziej użytecznym dla przyszłych czytelników z podobnymi, ale nie identycznymi pytaniami. Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia.
Toby Speight