Перевірте перекриття діапазонів дат у MySQL


82

Ця таблиця використовується для зберігання сеансів (подій):

CREATE TABLE session (
  id int(11) NOT NULL AUTO_INCREMENT
, start_date date
, end_date date
);

INSERT INTO session
  (start_date, end_date)
VALUES
  ("2010-01-01", "2010-01-10")
, ("2010-01-20", "2010-01-30")
, ("2010-02-01", "2010-02-15")
;

Ми не хочемо мати конфлікту між діапазонами.
Скажімо, нам потрібно вставити нову сесію з 05.01.2010 по 25.01.2010 .
Ми хотіли б знати конфліктні сесії.

Ось мій запит:

SELECT *
FROM session
WHERE "2010-01-05" BETWEEN start_date AND end_date
   OR "2010-01-25" BETWEEN start_date AND end_date
   OR "2010-01-05" >= start_date AND "2010-01-25" <= end_date
;

Ось результат:

+----+------------+------------+
| id | start_date | end_date   |
+----+------------+------------+
|  1 | 2010-01-01 | 2010-01-10 |
|  2 | 2010-01-20 | 2010-01-30 |
+----+------------+------------+

Чи є кращий спосіб отримати це?


скрипка


1
Ваша третя умова неправильна. Це повинно бути "2010-01-05" <= start_date AND "2010-01-25" >= end_date. Для візуалізації див. Stackoverflow.com/a/28802972/632951 . Ваша поточна третя умова ніколи не оцінить, оскільки перша (і друга) умова вже охоплює її.
Pacerier

Відповіді:


156

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

... WHERE new_start < existing_end
      AND new_end   > existing_start;

ОНОВЛЕННЯ Це однозначно має працювати ((ns, ne, es, ee) = (new_start, new_end, existing_start, existing_end)):

  1. ns - ne - es - ee: не перекривається і не збігається (оскільки ne <es)
  2. ns - es - ne - ee: перекриття та збіги
  3. es - ns - ee - ne: перекриття та збіги
  4. es - ee - ns - ne: не перекривається і не збігається (оскільки ns> ee)
  5. es - ns - ne - ee: перекриття та збіги
  6. ns - es - ee - ne: перекриття та збіги

Ось скрипка


7
Чудово працює !, але я думаю, що @Pierre de LESPINAY шукає в своєму запиті інклюзивні діапазони: WHERE new_start <= existing_end AND new_end> = existing_start;
Освальдо Меркадо

16
@OsvaldoM. Якби він справді був, він би скаржився приблизно 2 роки тому ...
soulmerge

Я сьогодні закінчую подію, а сьогодні розпочинається інша, чи вони збігаються? На мою думку, вони будуть збігатися. Але запит SQL цього не скаже: sqlfiddle.com/#!2/0a6fd/1/0
AL

2
@soulmerge, Насправді він просто додав би =до свого фактичного коду, замість того, щоб турбуватися про це.
Pacerier

32
SELECT * FROM tbl WHERE
existing_start BETWEEN $newStart AND $newEnd OR 
existing_end BETWEEN $newStart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

if (!empty($result))
throw new Exception('We have overlapping')

Ці 3 рядки пропозицій sql охоплюють 4 випадки необхідного перекриття.


7
Навіть якщо ОП, мабуть, не шукав цього перекриваючогося визначення, ця відповідь є найкращим рішенням проблеми, описаної назвою питання. Я шукав це перекриття, яке є справжнім перекриттям.
Cec

2
Я не впевнений, що ви маєте на увазі під "справжнім перекриттям", але головна відповідь @soulmerge еквівалентна цій відповіді Ламі. Я зміг використати вирішувач
теорем

16

Відповідь Ламі хороша, але ви можете трохи оптимізувати її.

SELECT * FROM tbl WHERE
existing_start BETWEEN $newSTart AND $newEnd OR
$newStart BETWEEN existing_start AND existing_end

Це охопить усі чотири сценарії, коли діапазони перекриваються, і виключає два, де вони відсутні.


Чи є інші рішення, крім цього та двох інших вище?
Pacerier

4

Я стикався з подібною проблемою. Моя проблема полягала в тому, щоб зупинити бронювання між діапазоном заблокованих дат. Наприклад, бронювання заблоковано для об'єкта з 2 травня по 7 травня. Мені потрібно було знайти будь-яку дату перекриття, щоб виявити та зупинити бронювання. Моє рішення схоже на LordJavac.

SELECT * FROM ib_master_blocked_dates WHERE venue_id=$venue_id AND 
(
    (mbd_from_date BETWEEN '$from_date' AND '$to_date') 
    OR
    (mbd_to_date BETWEEN  '$from_date' AND '$to_date')
    OR
    ('$from_date' BETWEEN mbd_from_date AND mbd_to_date)
    OR      
    ('$to_date' BETWEEN mbd_from_date AND mbd_to_date)      
)
*mbd=master_blocked_dates

Повідомте мене, якщо це не спрацює.


2

Враховуючи два інтервали, такі як (s1, e1) та (s2, e2) з s1 <e1 та s2 <e2,
можна обчислити перекриття таким чином:

SELECT 
     s1, e1, s2, e2,
     ABS(e1-s1) as len1,
     ABS(e2-s2) as len2,
     GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0)>0 as overlaps,
     GREATEST(LEAST(e1, e2) - GREATEST(s1, s2), 0) as overlap_length
FROM test_intervals 

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


0

Нещодавно я боровся з тією ж проблемою і закінчив цим одним простим кроком (Це може бути невдалим підходом або споживанням пам'яті) -

SELECT * FROM duty_register WHERE employee = '2' AND (
(
duty_start_date BETWEEN {$start_date} AND {$end_date}
OR
duty_end_date BETWEEN {$start_date} AND {$end_date}
)
OR
(
{$start_date} BETWEEN duty_start_date AND duty_end_date
OR
{$end_date} BETWEEN duty_start_date AND duty_end_date)
);

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

Сподіваюся, це комусь допомагає.


0

Ви можете охопити всі випадки перекриття дат, навіть коли поточна дата в базі даних може бути нульовою, як показано нижче:

SELECT * FROM `tableName` t
WHERE t.`startDate` <= $toDate
AND (t.`endDate` IS NULL OR t.`endDate` >= $startDate);

Це поверне всі записи, які так чи інакше перекриваються з новими датами початку / закінчення.


0

Відповідь Макракена вище є кращою з точки зору продуктивності, оскільки для оцінки перекриття двох дат не потрібно кілька АБО. Гарне рішення!

Однак я виявив, що в MySQL вам потрібно використовувати DATEDIFF замість оператора мінус -

SELECT o.orderStart, o.orderEnd, s.startDate, s.endDate
, GREATEST(LEAST(orderEnd, endDate) - GREATEST(orderStart, startDate), 0)>0 as overlaps
, DATEDIFF(LEAST(orderEnd, endDate), GREATEST(orderStart, startDate)) as overlap_length
FROM orders o
JOIN dates s USING (customerId)
WHERE 1
AND DATEDIFF(LEAST(orderEnd, endDate),GREATEST(orderStart, startDate)) > 0;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.