При разработке отчетов или обработок планирования часто возникает задача отобрать документы, время действия которых частично или полностью попадает в выбранный пользователем период. Например, у нас есть документ с полями ПланДатаНачала и ПланДатаОкончания, а на форме обработки выбран СтандартныйПериод. Нам нужно написать запрос так, чтобы в выборку попали все документы, «задевающие» этот период.
В этой статье мы разберем логику построения таких запросов, рассмотрим различные варианты условий и выберем наиболее оптимальный с точки зрения производительности и чистоты кода.
Чтобы понять, какое условие нам нужно, давайте проанализируем ситуацию. Представим, что у нас есть выбранный период планирования (от &НачДата до &КонДата). Документ должен попасть в выборку в следующих случаях:
Рассмотрим, как реализовать это в языке запросов 1С.
Самый изящный и производительный способ проверить пересечение двух интервалов [A, B] и [C, D] заключается в одном простом правиле: начало первого интервала должно быть меньше или равно конца второго, И конец первого интервала должен быть больше или равен началу второго.
В контексте нашей задачи условие в запросе будет выглядеть следующим образом:
ГДЕ
Документ.ПланДатаНачала <= &КонДата
И Документ.ПланДатаОкончания >= &НачДата
Разберем, почему это работает. Если ПланДатаНачала документа находится после &КонДата, значит, документ начнется уже после завершения интересующего нас периода. Если ПланДатаОкончания находится до &НачДата, значит, документ уже завершился к моменту начала периода. Инвертируя эти условия и соединяя их оператором И, мы получаем идеальный фильтр для всех четырех сценариев пересечения.
Если вам необходимо сделать код более «читаемым» для коллег или если бизнес-логика требует разного веса для разных типов пересечений, можно использовать конструкцию с оператором ИЛИ. Рассмотрим пример, который учитывает все условия в явном виде:
ГДЕ
(Документ.ПланДатаНачала МЕЖДУ &НачДата И &КонДата)
ИЛИ (Документ.ПланДатаОкончания МЕЖДУ &НачДата И &КонДата)
ИЛИ (Документ.ПланДатаНачала <= &НачДата И Документ.ПланДатаОкончания >= &КонДата)
Проанализируем это условие по шагам:
МЕЖДУ.Важно: Оператор МЕЖДУ в 1С включает границы периода. То есть Дата МЕЖДУ &НачДата И &КонДата эквивалентно Дата >= &НачДата И Дата <= &КонДата.
При написании подобных запросов начинающие разработчики часто совершают ошибки, связанные со спецификой хранения дат и ссылочных типов. Рассмотрим важные нюансы.
1. Границы периода. Если вы используете СтандартныйПериод, помните, что ДатаОкончания должна включать последнюю секунду дня. В коде перед передачей параметра в запрос обязательно используйте функцию КонецДня() для конечной даты, иначе документы, запланированные на вторую половину последнего дня периода, не попадут в выборку.
Запрос.УстановитьПараметр("НачДата", ПериодНаФорме.ДатаНачала);
Запрос.УстановитьПараметр("КонДата", КонецДня(ПериодНаФорме.ДатаОкончания));
2. Составные типы данных. Если поле Документ в вашем регистре или таблице имеет составной тип, обращение через точку (Документ.ПланДатаНачала) приведет к существенному замедлению запроса. Система будет вынуждена выполнить LEFT JOIN со всеми таблицами документов, входящими в этот тип.
Проанализируем правильный подход: используйте функцию ВЫРАЗИТЬ, чтобы подсказать системе конкретную таблицу, или сначала выберите ссылки во временную таблицу.
ГДЕ
ВЫРАЗИТЬ(СЦентр_ЗагрузкаМастеров.Документ КАК Документ.Выезд).ПланДатаНачала <= &КонДата
И ВЫРАЗИТЬ(СЦентр_ЗагрузкаМастеров.Документ КАК Документ.Выезд).ПланДатаОкончания >= &НачДата
3. Пустые даты. Выясним, как ведет себя система, если ПланДатаОкончания не заполнена (равна 01.01.0001). В этом случае условие >= &НачДата не сработает. Если в вашей логике незаполненная дата окончания означает «бессрочно», добавьте проверку на пустую дату:
ГДЕ
Документ.ПланДатаНачала <= &КонДата
И (Документ.ПланДатаОкончания >= &НачДата ИЛИ Документ.ПланДатаОкончания = ДАТАВРЕМЯ(1, 1, 1))
С точки зрения СУБД, вариант №1 (с оператором И) предпочтительнее варианта №2 (с оператором ИЛИ). При использовании ИЛИ оптимизатор базы данных часто отказывается от использования индексов и переходит к полному сканированию таблицы (Index Scan), что критично при больших объемах данных.
Для максимально быстрой работы запроса убедитесь, что поля ПланДатаНачала и ПланДатаОкончания индексированы в свойствах объекта конфигурации.
Подводя итог, можно сказать, что наиболее стабильным и лаконичным решением является проверка пересечения границ через два простых сравнения. Это покрывает все бизнес-кейсы и обеспечивает высокую скорость выполнения кода.