Проверка таблицы на время перекрытия? - PullRequest
16 голосов
/ 04 июля 2011

У меня есть таблица MySQL со следующими полями:

  • имя
  • время_запуск
  • 1008 * время окончания *

starttime и endtime являются полями MySQL TIME (не DATETIME). Мне нужен способ периодически «сканировать» таблицу, чтобы увидеть, есть ли какие-либо совпадения во временных диапазонах в таблице. Если есть событие от 10:00-11:00 и другое от 10:30-11:30, я хочу быть предупрежден о наличии перекрытия времени.

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

Я собираюсь использовать PHP для выполнения этого.

Ответы [ 4 ]

32 голосов
/ 04 июля 2011

Это шаблон запроса, на который я нашел ответ много лет назад:

SELECT *
FROM mytable a
JOIN mytable b on a.starttime <= b.endtime
    and a.endtime >= b.starttime
    and a.name != b.name; -- ideally, this would compare a "key" column, eg id

Чтобы найти «любое перекрытие», вы сравниваете противоположные концы таймфрейма с каждымДругой.Мне нужно было достать ручку и бумагу и нарисовать соседние диапазоны, чтобы понять, что грани границ сводятся к этому сравнению.


Если вы хотите предотвратить наложение строк, поместите вариантэтого запроса в триггере:

create trigger mytable_no_overlap
before insert on mytable
for each row
begin
  if exists (select * from mytable
             where starttime <= new.endtime
             and endtime >= new.starttime) then
    signal sqlstate '45000' SET MESSAGE_TEXT = 'Overlaps with existing data';
  end if;
end;
1 голос
/ 03 мая 2016

Я хотел, чтобы универсальная функция проверяла, перекрываются ли два временных диапазона для дней, что также будет работать в тех случаях, когда расписание начинается до полуночи и заканчивается после, например «17: 00: 00-03: 00: 00» и «14 : 00: 00-01: 00: 00 "должны перекрываться, поэтому я изменил решение на Bohemian

Вы используете эту функцию следующим образом

SELECT func_time_overlap("17:00:00","03:00:00", "14:00:00","01:00:00")

или в вашем случае вот так

SELECT *
FROM mytable a
JOIN mytable b ON (
    a.name != b.name 
    AND func_time_overlap(a.starttime, a.endtime, b.starttime, b.endtime)
);

Вот определение функции

CREATE FUNCTION `func_time_overlap`(a_start TIME, a_end TIME, b_start TIME, b_end TIME) 
RETURNS tinyint(1) 
DETERMINISTIC
BEGIN

-- there are only two cases when they don't overlap, but a lot of possible cases where they do overlap

-- There are two time formats, one is an interval of time that can go over 24 hours, the other is a daily time format that never goes above 24 hours
-- by default mysql uses TIME as an interval
-- this converts a TIME interval into a date time format

-- I'm not using `TIME(CAST(a_start AS DATETIME));` to convert the time interval to a time
-- because it uses the current day by default and might get affected by the timezone settings of the database, 
-- just imagine the next day having the DST change.
-- although the CAST should work fine if you use UTC

IF a_start >= 24 THEN 
    SET a_start = TIME(CONCAT(MOD(HOUR(a_start), 24),':',MINUTE(a_start),':',SECOND(a_start))); 
END IF;

IF b_start >= 24 THEN 
    SET b_start = TIME(CONCAT(MOD(HOUR(b_start), 24),':',MINUTE(b_start),':',SECOND(b_start))); 
END IF;

IF a_end > 24 THEN 
    SET a_end = TIME(CONCAT(MOD(HOUR(a_end), 24),':',MINUTE(a_end),':',SECOND(a_end))); 
END IF;

IF b_end > 24 THEN 
    SET b_end = TIME(CONCAT(MOD(HOUR(b_end), 24),':',MINUTE(b_end),':',SECOND(b_end))); 
END IF;


-- if the time range passes the midnight mark, then add 24 hours to the time
IF a_start >= a_end THEN 
    SET a_end = a_end + INTERVAL 24 HOUR; 
END IF;

IF b_start >= b_end THEN 
    SET b_end = b_end + INTERVAL 24 HOUR; 
END IF;

RETURN a_start < b_end AND a_end > b_start;


END

Я не использую TIME(CAST(a_start AS DATETIME)); для преобразования временного интервала во время, потому что он использует текущий день по умолчанию и может зависеть от настроек часового пояса базы данных, просто представьте, что на следующий день произойдет изменение DST.

Если ваша база данных использует часовой пояс UTC (как и должно быть), вы можете использовать это

IF a_start >= 24 THEN 
    SET a_start = TIME(CAST(a_start AS DATETIME)); 
END IF;

IF b_start >= 24 THEN 
    SET b_start = TIME(CAST(b_start AS DATETIME)); 
END IF;

IF a_end > 24 THEN 
    SET a_end = TIME(CAST(a_end AS DATETIME));
END IF;

IF b_end > 24 THEN 
    SET b_end = TIME(CAST(b_end AS DATETIME));
END IF;
0 голосов
/ 18 мая 2018

Попробуйте, у меня все работает

SELECT * from Shedulles a 
where exists 
( select 1 from Shedulles b 
    where 
    a.ShedulleId != b.ShedulleId 
    and ( a.DateFrom between b.DateFrom and b.DateTo 
    or a.DateTo between b.DateFrom and b.DateTo 
    or b.DateFrom between a.DateFrom and a.DateTo ) 
    and a.DateFrom != b.DateTo 
    and b.DateFrom != a.DateTo 
);

Или этот

SELECT DISTINCT a.* FROM Shedulles a
JOIN Shedulles b 
    ON 
    a.ShedulleId != b.ShedulleId 
    and ( a.DateFrom between b.DateFrom and b.DateTo 
    or a.DateTo between b.DateFrom and b.DateTo 
    or b.DateFrom between a.DateFrom and a.DateTo ) 
    and a.DateFrom != b.DateTo 
    and b.DateFrom != a.DateTo 
0 голосов
/ 04 июля 2011

Попробуйте это:

declare @tempTbl table(RecID)

    insert into @tempTbl
    Select RecID
    from 
    (
    Select t.RecID from Table1 t,Table1 t1
    where t.StartTime between t1.StartTime AND t1.EndTime
    AND t.RecID <> t1.RecID  

    )
...