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


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

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

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

Вам нужно будет решить, находятся ли следующие периоды В или ВНЕ ваших диапазонов:

           |-------------|
   |-------|                       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» для ответов, который позволит вам проголосовать за них более одного раза
Джонни Бьюкенен,

29
Вероятно, один из 5 лучших ответов, которые я видел на SO. Отличное объяснение проблемы, хорошее пошаговое руководство и ... картинки!
davidavr

10
Если бы я мог проголосовать за это более одного раза, я бы это сделал. Великолепное, ясное и краткое объяснение часто возникающей проблемы, решение, которое я редко видел так хорошо объясненным!
ConroyP

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

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

8

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

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

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


4

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


1
Эпическое решение. Работает отлично. Это синтаксис для 2 диапазонов дат (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

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

Добро пожаловать в Stack Overflow! Благодарим вас за этот фрагмент кода, который может оказать немедленную помощь. Правильное объяснение значительно повысило бы его образовательную ценность, показав, почему это хорошее решение проблемы, и сделало бы его более полезным для будущих читателей с похожими, но не идентичными вопросами. Пожалуйста , измените свой ответ , чтобы добавить объяснение, и дать указание о том , что применять ограничения и допущения.
Тоби Спейт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.