Порівняння діапазонів дат


116

У MySQL, Якщо у мене є список діапазонів дат (діапазон-початок і діапазон-кінець). напр

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

І я хочу перевірити, чи містить інший діапазон дат будь-який діапазон, який уже є у списку, як би це зробити?

напр

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

Відповіді:


439

Це класична проблема, і насправді простіше, якщо ви перейдете логіку.

Дозвольте навести вам приклад.

Я розміщу тут один проміжок часу, і всі різні варіанти інших періодів, які певним чином перетинаються.

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

з іншого боку, дозвольте мені опублікувати всі ті, що не перетинаються:

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

Тож якщо ви просто зменшіть порівняння до:

starts after end
ends before start

тоді ви знайдете всі ті, що не перетинаються, і тоді ви знайдете всі невідповідні періоди.

Для останнього прикладу NOT IN LIST ви можете бачити, що він відповідає цим двом правилам.

Вам потрібно буде прийняти рішення про те, щоб наступні періоди були ВІН або ВНЕШІ вашими діапазонами:

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

Якщо у вашій таблиці є стовпці з назвою range_end та range_start, ось кілька простих SQL для отримання всіх відповідних рядків:

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

Зверніть увагу на НЕ . Оскільки два простих правила знаходять всі невідповідні рядки, простий НЕ поверне це слово: якщо це не один із невідповідних рядків, він повинен бути одним із відповідних .

Застосовуючи тут просту логіку зміни, щоб позбутися від НЕ, і ви закінчите:

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

45
Нам потрібен прапор "містить діаграми ACII", щоб відповісти, які дозволяють вам оприлюднити їх не один раз
Jonny Buchanan,

29
Напевно, одна з 5 найкращих відповідей, які я бачив на SO. Чудове пояснення проблеми, приємне проходження рішення та ... фотографії!
davidavr

10
Якби я міг проголосувати за це не один раз, я би. Чудове, чітке і стисле пояснення поширеного питання, яке виникає, рішення якого я рідко бачив так добре пояснено!
ConroyP

2
Чудова відповідь! Єдине, що я хотів би додати - стосовно вирішення того, включаються кінцеві точки чи ні - все виходить більш чистим, якщо ви йдете із закритим інтервалом з одного боку та відкритим інтервалом з іншого. Наприклад, початок діапазону включає точку, а кінець діапазону не відповідає. Особливо, коли ви маєте справу з поєднанням дат та часу різних дозволів, все стає простішим.
Eclipse

1
Хороша відповідь. Це також описано як інтервальну алгебру Аллена . Я маю подібну відповідь і вступив у жорстокий бій за те, скільки існує різних порівнянь з одним коментатором.
Джонатан Леффлер

8

Беручи до прикладу діапазон від 06.06.1983 по 18.06.1983 та припускаючи, що у вас є стовпці, що називаються початком і кінцем для ваших діапазонів, ви можете використовувати такий пункт

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

тобто перевірити початок вашого тестового діапазону до кінця діапазону баз даних, а також те, що кінець вашого тестового діапазону знаходиться після або після початку діапазону бази даних.


4

Якщо ваш RDBMS підтримує функцію OVERLAP (), це стає тривіальним - немає потреби в доморощених рішеннях. (В Oracle це апарат працює, але недокументовано).


1
Епічний розчин. Добре працює. Це синтаксис для двох діапазонів дат (s1, e1) та (s2, e2) в Oracle: виберіть 1 з подвійних, де (s1, e1) перекривається (s2, e2);
ihebiheb

0

У своїх очікуваних результатах ви говорите

06.06.1983 по 18.06.1983 = В СПИСОКІ

Однак цей період не містить і не містить жодного з періодів у вашій таблиці (не перелік!) Періодів. Однак це перетинає період з 10.06.1983 р. По 14.06.1983 р.

Ви можете вважати корисною книгу про Snodgrass ( http://www.cs.arizona.edu/people/rts/tdbbook.pdf ): вона зазначає mysql, але поняття часу не змінилося ;-)


0

Я створив функцію для вирішення цієї проблеми в MySQL. Просто перетворіть дати в секунди перед використанням.

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

Розгляньте наступний приклад. Це вам допоможе.

    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

Спробуйте це в 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

Інший метод за допомогою оператора BETWEEN sql

Періоди включені:

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

Періоди виключені:

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

Ласкаво просимо до переповнення стека! Дякуємо за цей фрагмент коду, який може надати негайну допомогу. Правильне пояснення значно покращило б його навчальну цінність, показавши, чому це хороше рішення проблеми, та зробило б її кориснішою для майбутніх читачів із подібними, але не однаковими питаннями. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Toby Speight
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.