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

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

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

Ответ 1

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

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

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

           |-------------------|          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 вы можете видеть, что он соответствует этим двум правилам.

Вам нужно будет решить, что следующие периоды: IN или OUTSIDE диапазоны:

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

Обратите внимание на NOT. Поскольку два простых правила находят все несоответствующие строки, простой NOT обратит его, чтобы сказать: если он не является одной из несоответствующих строк, он должен быть одним из соответствующих.

Применение простой логики разворота здесь, чтобы избавиться от NOT, и вы получите:

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

Ответ 2

Принимая ваш пример с 06/06/1983 по 18/06/1983 и предполагая, что у вас есть столбцы с названием начать и конец для ваших диапазонов, вы можете использовать как это

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

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

Ответ 3

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

Ответ 4

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

06/06/1983 - 18/06/1983 = IN LIST

Однако этот период не содержит и не содержит ни один из периодов в вашей таблице (не список!) периодов. Однако он накладывается на период с 10/06/1983 по 14/06/1983.

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

Ответ 5

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

Ответ 6

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

    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 [email protected]) and  nfl.UserId = @userid and nfl.RelatedSettingColumn = RelatedTo

Ответ 7

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;

Ответ 8

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

Ответ 9

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