Рабочие дни в SQL: считаем срок согласования по производственному календарю
Заявка поступила в пятницу в 16:30, согласована в понедельник в 10:15. Календарная разница включает выходные и ночи. Если срок измеряется рабочим временем, нужен производственный календарь с рабочими интервалами. Сложим пересечения заявки с этими интервалами и получим длительность, которую можно сравнить с регламентом.
Календарь должен описывать правило процесса
В примере рабочий день длится с 09:00 до 18:00, суббота и воскресенье нерабочие. Пятница даёт 90 минут, понедельник — 75, всего 165. Для другого графика достаточно изменить календарь; запрос не должен угадывать праздники по номеру дня недели.
Храните начало и конец каждого рабочего интервала. Сокращённый день, обед или отдельный график подразделения выражаются строками календаря. Если интервалов несколько за день, они не должны перекрываться, иначе одни минуты будут посчитаны дважды.
Найдите длину каждого пересечения
Пересечение существует, когда начало заявки раньше конца рабочего интервала, а завершение позже его начала. Начало пересечения — более поздняя из двух начальных границ, конец — более ранняя конечная. Разницу переводим в минуты и суммируем.
Самодостаточный запрос ниже использует SQLite и ISO-строки одного локального времени без перевода часовых поясов. unixepoch переводит границы в секунды; /60.0 сохраняет дробные минуты. Для реального процесса заранее согласуйте часовой пояс и поведение при его сезонных изменениях.
WITH requests(id,opened,closed) AS (
VALUES (1,'2026-11-06 16:30:00','2026-11-09 10:15:00'),
(2,'2026-11-07 12:00:00','2026-11-09 09:00:00')
), calendar(work_start,work_end) AS (
VALUES ('2026-11-06 09:00:00','2026-11-06 18:00:00'),
('2026-11-09 09:00:00','2026-11-09 18:00:00')
)
SELECT r.id,
SUM(CASE WHEN c.work_start IS NULL THEN 0 ELSE
(unixepoch(MIN(r.closed,c.work_end)) -
unixepoch(MAX(r.opened,c.work_start)))/60.0 END) AS work_minutes
FROM requests r LEFT JOIN calendar c
ON r.opened < c.work_end AND r.closed > c.work_start
GROUP BY r.id ORDER BY r.id;
Проверьте нулевое рабочее время
Вторая заявка открыта в субботу и закрыта в понедельник ровно в 09:00. Она не пересекает рабочие интервалы и получает ноль. LEFT JOIN сохраняет её строку, а CASE не позволяет NULL превратить результат в неопределённое значение.
Заявка с отсутствующей датой завершения — отдельная задача. Для текущего возраста подставьте явно заданный момент среза, сохранив признак открытой заявки. Не используйте случайное текущее время в тесте: результат будет меняться между запусками.
Согласуйте, что считать рабочим днём
Результат запроса — минуты. Деление на 540 даст эквивалент девятичасового дня только при соответствующем правиле регламента. Если графики различаются, «два рабочих дня» может означать дату по календарю, а не фиксированное число часов. Назовите показатель точно до сравнения с SLA.
Для приёмки добавьте заявку целиком внутри одного дня, заявку на границе открытия и закрытия, праздничный день и сокращённую пятницу. Сверьте ожидаемые минуты вручную. Затем проверьте, что календарь покрывает весь диапазон заявок: отсутствующий рабочий день нельзя молча считать выходным.
Сохраните версию календаря. Исправление праздника задним числом изменит длительность уже завершённых процессов. В отчёте должна быть возможность понять, по какому графику рассчитан срок.
Если процесс проходит несколько отделов с разными графиками, сначала разбейте историю на этапы и для каждого примените нужный календарь. Один общий график может дать технически точную разницу, которая не соответствует правилам реальной работы.
От запроса к рабочей витрине
В курсе вы будете строить календарные ряды с пропусками и работать с временными интервалами в SQLite. Автоматическая проверка включает граничные случаи и пустые выборки. Эти навыки помогают проверить не только формулу длительности, но и полноту календаря, от которого зависит рабочий срок.
Откройте программу платного курса «Продвинутый SQL: сложные запросы и задачи для собеседований».
Продолжить разбор: Просроченные заявки в SQL: почему открытые тикеты нельзя выбрасывать из SLA.
Продолжить на практике
- Продвинутый SQL: сложные запросы и задачи для собеседований ↗
Продолжение базового SQL: сложные JOIN, оконные рамки, временные ряды, retention, воронки и рекурсивные CTE. Разбирайте примеры и пишите запросы с автоматической проверкой — от отдельных расчётов до витрины интернет-магазина.
