Сравнение диапазонов дат

В MySQL, если у меня есть список диапазонов дат (начало и конец диапазона). например

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

И я хочу проверить, содержит ли другой диапазон дат ЛЮБОЙ из диапазонов, уже включенных в список, как мне это сделать?

e.g.

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

person Kieran Benton    schedule 27.09.2008    source источник
comment
возможный дубликат Определить, перекрываются ли два диапазона дат   -  person Salman A    schedule 03.09.2015


Ответы (9)


Это классическая проблема, и ее действительно проще, если вы перевернете логику.

Позвольте привести пример.

Я размещу здесь один период времени и все различные вариации других периодов, которые так или иначе пересекаются.

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

тогда вы найдете все те, которые не пересекаются, а затем вы найдете все несовпадающие периоды.

В вашем последнем примере НЕТ В СПИСКЕ, вы можете видеть, что он соответствует этим двум правилам.

Вам нужно будет решить, находятся ли следующие периоды ВХОДА или ЗА пределами вашего диапазона:

           |-------------|
   |-------|                       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
person Lasse V. Karlsen    schedule 27.09.2008
comment
Нам нужен флаг содержит диаграммы ACII для ответов, который позволит вам проголосовать за них более одного раза. - person Jonny Buchanan; 27.09.2008
comment
Вероятно, один из 5 лучших ответов, которые я видел на SO. Отличное объяснение проблемы, хорошее пошаговое руководство и ... картинки! - person davidavr; 27.09.2008
comment
Да, это было абсолютно великолепно, спасибо, абсолютно неправильно смотрел на это и боролся с датами в моей голове. Только что написал свои запросы, используя обратное, и теперь это очень ясно! - person Kieran Benton; 27.09.2008
comment
Супер ответ! Абсолютно люблю то, как это было ясно объяснено. - person Learning; 27.09.2008
comment
Если бы я мог проголосовать за это более одного раза, я бы это сделал. Великолепное, ясное и краткое объяснение часто возникающей проблемы, решение которой я редко видел так хорошо объясненным! - person ConroyP; 27.09.2008
comment
Отличный ответ! Единственное, что я бы добавил - в отношении решения, включены ли конечные точки или нет - все работает чище, если вы используете закрытый интервал с одной стороны и открытый интервал с другой. Например. начало диапазона входит в точку, а конец диапазона - нет. Все становится проще, особенно когда вы имеете дело с комбинацией дат и времени различного разрешения. - person Eclipse; 30.11.2010
comment
Хороший ответ. Это также называется интервальной алгеброй Аллена. У меня есть аналогичный ответ, и я вступил в ожесточенную битву за сколько разных сравнений с одним комментатором. - person Jonathan Leffler; 19.01.2011
comment
Я ломал себе голову из-за подобной проблемы, и меня поразило, как я не увидел такого подхода. Большое спасибо за эту блестящую точку зрения, и искусство ASCII - person Metalmini; 27.09.2012
comment
Единственное, что я бы добавил, это то, что для того, чтобы это сработало, вам необходимо проверить свои диапазоны дат, чтобы конец никогда не был раньше начала, что не является проблемой для гораздо более сложного необратимого метода. - person MikeT; 07.10.2013
comment
Спасибо за ответ. Предположим, мы используем систему бронирования. Как изменить SQL, если мы хотим, чтобы покупатель нашел free dates в системе бронирования? Особенно интересно то, как найти способности к бронированию holes. Это означает, что у нас есть 2 резервирования в системе, например 2014-09-01 -> 2014-09-10 и 2014-09-15 -> 2014-09-20. Как найти free hole из 2014-09-10 to 2014-09-15? - person Lari13; 02.09.2014

Взяв в качестве примера диапазон от 06.06.1983 до 18.06.1983 и предположив, что у вас есть столбцы с названиями начало и конец для ваших диапазонов, вы можете использовать такое предложение

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

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

person Paul Dixon    schedule 27.09.2008

Если ваша СУБД поддерживает функцию OVERLAP (), это становится тривиальным делом - нет необходимости в собственных решениях. (В Oracle это явно работает, но не документировано).

person David Aldridge    schedule 28.09.2008
comment
Эпическое решение. Работает отлично. Это синтаксис для двух диапазонов дат (s1, e1) и (s2, e2) в Oracle: выберите 1 из двойных, где (s1, e1) перекрываются (s2, e2); - person ihebiheb; 16.11.2015

В ваших ожидаемых результатах вы говорите

06.06.1983 по 18.06.1983 = В СПИСКЕ

Однако этот период не содержит и не входит ни в один из периодов в вашей таблице (не в списке!) Периодов. Однако он перекрывает период с 10.06.1983 по 14.06.1983.

Вы можете найти книгу Snodgrass (http://www.cs.arizona.edu/people/rts/tdbbook.pdf) полезно: он предшествует mysql, но концепция времени не изменилась ;-)

person onedaywhen    schedule 29.09.2008

Я создал функцию для решения этой проблемы в 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 ;
person jonavon    schedule 03.05.2011

Посмотрите на следующий пример. Это будет вам полезно.

    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
person Rama Subba Reddy M    schedule 19.12.2011

Попробуйте это на 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];
person RickyS    schedule 07.01.2015

Другой метод с использованием инструкции 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)
person Florian HENRY - Scopen    schedule 15.10.2015

person    schedule
comment
Добро пожаловать в Stack Overflow! Благодарим вас за этот фрагмент кода, который может оказать немедленную помощь. Правильное объяснение значительно повысило бы его образовательную ценность, показав почему это хорошее решение проблемы, и сделает его более полезным для будущих читателей с похожими, но не идентичными вопросами. Пожалуйста, отредактируйте свой ответ, чтобы добавить пояснения и указать, какие ограничения и допущения применяются. - person Toby Speight; 22.06.2017