В практике разработки на платформе 1С часто возникает задача разделить поток документов по рабочим сменам. Типичный пример: дневная смена длится с 08:30 до 20:30, а ночная — с 20:30 до 08:30 следующего дня. Основная сложность заключается в том, что стандартные функции языка запросов, такие как НАЧАЛОПЕРИОДА, работают только с целыми датами, часами или днями, а нам требуется точность до минуты.
В этой статье мы подробно разберем, как реализовать подобную логику, рассмотрим несколько способов решения и выясним, какой из них наиболее оптимален с точки зрения производительности системы.
Прежде чем пытаться написать сложное условие внутри самого запроса, проанализируем ситуацию с точки зрения производительности. Использование функций над полями таблиц в блоке ГДЕ (например, ЧАС(Дата) или РАЗНОСТЬДАТ(...)) приводит к тому, что СУБД не может использовать индекс по полю Дата. Это вызывает полное сканирование таблицы (Full Table Scan), что на больших объемах данных приведет к значительным задержкам.
Самый правильный метод — рассчитать точные моменты начала и окончания смены в коде на языке 1С и передать их в запрос как готовые параметры. Рассмотрим, как это сделать для дневной смены:
// Предположим, у нас есть ДатаОтчета
НачалоДня = НачалоДня(ДатаОтчета);
НачалоСмены = НачалоДня + 8 * 3600 + 30 * 60; // 08:30:00
КонецСмены = НачалоДня + 20 * 3600 + 30 * 60; // 20:30:00
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Отчет.Ссылка КАК Ссылка
|ИЗ
| Документ.ОтчетПроизводстваЗаСмену КАК Отчет
|ГДЕ
| Отчет.Дата МЕЖДУ &НачалоСмены И &КонецСмены";
Запрос.УстановитьПараметр("НачалоСмены", НачалоСмены);
Запрос.УстановитьПараметр("КонецСмены", КонецСмены);
Этот способ позволяет базе данных мгновенно найти нужные записи, используя индекс. Если вам нужно выбрать документы "ночной смены", вы аналогично рассчитываете два интервала (вечер текущего дня и утро следующего) или один длинный интервал, если смена переходит через полночь.
Если по каким-то причинам (например, в рамках сложного динамического списка или схемы компоновки данных) вам необходимо вычислить время именно внутри запроса, мы не можем ограничиться функцией ЧАС(), так как она проигнорирует минуты. Чтобы учесть 30 минут, лучше всего перевести время документа в секунды, прошедшие с начала дня.
Алгоритм расчета:
1. 8 часов 30 минут — это (8 * 3600) + (30 * 60) = 30 600 секунд.
2. 20 часов 30 минут — это (20 * 3600) + (30 * 60) = 73 800 секунд.
Проанализируем пример запроса, использующий функцию РАЗНОСТЬДАТ для получения секунд с начала суток:
ВЫБРАТЬ
Отчет.Ссылка,
Отчет.Дата
ИЗ
Документ.ОтчетПроизводстваЗаСмену КАК Отчет
ГДЕ
Отчет.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&ВыбДата, ДЕНЬ) И КОНЕЦПЕРИОДА(&ВыбДата, ДЕНЬ)
И РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(Отчет.Дата, ДЕНЬ), Отчет.Дата, СЕКУНДА) МЕЖДУ 30600 И 73800
В данном примере мы сначала ограничиваем выборку текущим днем (чтобы сработал индекс по дате), а затем уточняем время с точностью до секунды. Функция РАЗНОСТЬДАТ с параметром СЕКУНДА между началом дня документа и его реальной датой вернет нам количество секунд, прошедших с полуночи.
Ситуация усложняется, если нам нужно выбрать "Ночную смену", которая начинается в 20:30 одного дня и заканчивается в 08:30 другого. Если мы рассматриваем документы в рамках одних календарных суток (как просил автор темы), то ночная смена разбивается на два периода: с 00:00 до 08:30 и с 20:30 до 23:59.
Разберем, как реализовать такое условие с помощью конструкции ВЫБОР или логического ИЛИ:
ВЫБРАТЬ
Отчет.Ссылка
ИЗ
Документ.ОтчетПроизводстваЗаСмену КАК Отчет
ГДЕ
Отчет.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&ВыбДата, ДЕНЬ) И КОНЕЦПЕРИОДА(&ВыбДата, ДЕНЬ)
И (РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(Отчет.Дата, ДЕНЬ), Отчет.Дата, СЕКУНДА) < 30600
ИЛИ РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(Отчет.Дата, ДЕНЬ), Отчет.Дата, СЕКУНДА) >= 73800)
Здесь условие < 30600 отсекает утренние часы до 08:30, а >= 73800 захватывает вечернее время после 20:30. Это позволяет корректно собрать все "ночные" документы, созданные в течение выбранных суток.
Рассмотрим ситуацию, когда в запрос передается конкретный момент времени &ВыбДата, и нам нужно автоматически определить: если это время дневное — выбираем дневные документы, если ночное — ночные. Выясним, как это реализовать через ВЫБОР:
ВЫБРАТЬ
Отчет.Ссылка
ИЗ
Документ.ОтчетПроизводстваЗаСмену КАК Отчет
ГДЕ
Отчет.Дата МЕЖДУ НАЧАЛОПЕРИОДА(&ВыбДата, ДЕНЬ) И КОНЕЦПЕРИОДА(&ВыбДата, ДЕНЬ)
И ВЫБОР
// Проверяем, является ли переданная дата дневной сменой
КОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(&ВыбДата, ДЕНЬ), &ВыбДата, СЕКУНДА) МЕЖДУ 30600 И 73799
ТОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(Отчет.Дата, ДЕНЬ), Отчет.Дата, СЕКУНДА) МЕЖДУ 30600 И 73799
// Иначе считаем, что это ночная смена
ИНАЧЕ РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(Отчет.Дата, ДЕНЬ), Отчет.Дата, СЕКУНДА) < 30600
ИЛИ РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(Отчет.Дата, ДЕНЬ), Отчет.Дата, СЕКУНДА) >= 73800
КОНЕЦ
Важные замечания по реализации:
ЧАС(Дата) + МИНУТА(Дата)/60 могут привести к ошибкам округления в языке запросов.< или <=). Обычно начало периода включается (>=), а конец — нет, чтобы избежать дублирования документов на стыке смен.ЧАС() и РАЗНОСТЬДАТ() работают с датой "как она есть" в базе данных. Если сервер и клиенты находятся в разных часовых поясах, убедитесь, что вы сравниваете данные в одной временной шкале.Подведем итог: хотя язык запросов 1С предоставляет инструменты для манипуляции временем, наиболее грамотным подходом остается предварительный расчет границ периодов в коде. Это гарантирует высокую скорость работы программы и чистоту кода запроса. Однако, если расчет внутри запроса неизбежен, использование РАЗНОСТЬДАТ по секундам является самым надежным и точным техническим решением.