Первый свободный слот в SQL: найдём окно нужной длительности

В календаре кабинета есть свободные промежутки, но пациенту нужны непрерывные 45 минут. Первое свободное начало ещё не означает подходящий слот: до следующей записи может остаться всего полчаса. Задача поиска отличается от проверки уже предложенного бронирования. Нужно получить самое раннее время, в которое целый интервал помещается без пересечений.

Искать только там, где может начаться свободное окно

Пусть рабочее время — с 09:00 до 12:00, приём длится 45 минут. Записи занимают 09:00–10:00 и 10:30–11:00. Начало подходящего окна может совпасть с началом рабочего времени либо с концом одной из записей. Перебирать все минуты не нужно: между такими точками состояние занятости не меняется.

В примере время представлено целым числом минут от полуночи: 540 — 09:00, 600 — 10:00, 720 — 12:00. Интервалы полуоткрытые: конец предыдущего приёма может совпасть с началом следующего. Требование пяти минут на уборку следует выразить расширением занятого интервала, а не надеяться, что строгое неравенство само добавит буфер.

Проверить каждого кандидата целиком

Для кандидата c строим окно от c до c + 45. Пересечение есть, если существующая запись начинается раньше конца окна и заканчивается позже его начала. Оба условия должны выполняться одновременно. Затем отсеиваем окна, выходящие за рабочее время, и выбираем минимальное оставшееся начало.

Пример выполняется в SQLite; функции и синтаксис ниже относятся к этому диалекту.

WITH bookings(start_min,end_min) AS (VALUES (540,600),(630,660)),
candidates(start_min) AS (
 VALUES(540) UNION SELECT end_min FROM bookings WHERE end_min>=540
)
SELECT MIN(c.start_min) AS first_slot
FROM candidates c
WHERE c.start_min+45<=720
 AND NOT EXISTS (
  SELECT 1 FROM bookings b
  WHERE b.start_min<c.start_min+45 AND b.end_min>c.start_min
 );

Ответ — 660, то есть 11:00. Кандидат 09:00 пересекается с первым приёмом. Кандидат 10:00 оставляет окно до 10:45, которое пересекает запись с 10:30. В 11:00 можно начать и закончить в 11:45. Одна проверка «начальная минута не занята» ошибочно выбрала бы 10:00.

Если бронирования перекрываются

Проверка NOT EXISTS смотрит все записи, поэтому остаётся корректной и при наложениях. Например, завершение одного приёма может лежать внутри другого: такой кандидат будет отклонён. Объединять интервалы для правильности именно этого запроса необязательно. Для большого календаря объединение может сократить объём проверки, но оно требует собственной проверенной логики.

Возврат NULL означает, что подходящего кандидата в заданном окне нет. В интерфейсе это отдельное состояние, а не время 00:00. Если нужен поиск на несколько дней, сначала сформируйте рабочие интервалы по календарю кабинета, включая перерывы, и ищите в каждом. Не допускайте, чтобы приём автоматически перешёл через обед или границу рабочего дня.

Найти ещё не значит забронировать

Пока пользователь выбирает слот, другой человек может занять то же время. Поисковый SELECT не гарантирует сохранения свободного состояния до записи. Окончательная операция бронирования должна повторно проверить пересечение в согласованной транзакционной модели и атомарно зафиксировать результат. В SQLite нужно учитывать правила блокировок записи; в другой СУБД могут применяться другие механизмы ограничения пересечений.

Для нескольких кабинетов добавьте ресурс в бронирования и проверку пересечения. Если нужны одновременно врач и оборудование, свободное время требуется у каждого обязательного ресурса. Это уже пересечение доступностей. Нельзя просто взять первый свободный слот врача и независимо первый свободный слот аппарата: их время может не совпасть.

Практический набор приёмки включает запись, заканчивающуюся ровно в начале кандидата, запись, начинающуюся ровно в его конце, короткий промежуток, полностью занятый день и отсутствие записей. Сверьте результат с изображением календаря для этих случаев. Если длительность произвольная и начинается с секунд, замените минутную модель временными метками, сохранив правило полуоткрытых интервалов и единый часовой пояс.

Следующий шаг

Чтобы расширить поиск до нескольких рабочих интервалов и ресурсов, пригодятся CTE, календарные ряды и аккуратные условия соединения. В платном «Продвинутом SQL» есть практика интервалов, оконных функций и самостоятельных многошаговых запросов с автоматической проверкой. Так можно отработать граничные случаи до переноса расчёта в календарь записи. Откройте программу курса.

Продолжить на практике