Как правильно настроить условие пересечения периодов в запросе 1С?

Программист 1С v8.3 (Управляемые формы) Сфера услуг, туризм и социальный сектор
← На главную

При разработке отчетов или обработок планирования часто возникает задача отобрать документы, время действия которых частично или полностью попадает в выбранный пользователем период. Например, у нас есть документ с полями ПланДатаНачала и ПланДатаОкончания, а на форме обработки выбран СтандартныйПериод. Нам нужно написать запрос так, чтобы в выборку попали все документы, «задевающие» этот период.

В этой статье мы разберем логику построения таких запросов, рассмотрим различные варианты условий и выберем наиболее оптимальный с точки зрения производительности и чистоты кода.

Анализ возможных сценариев пересечения

Чтобы понять, какое условие нам нужно, давайте проанализируем ситуацию. Представим, что у нас есть выбранный период планирования (от &НачДата до &КонДата). Документ должен попасть в выборку в следующих случаях:

  1. Полное вхождение: Период документа целиком находится внутри выбранного интервала.
  2. Поглощение: Период документа шире выбранного интервала (начался раньше, закончился позже).
  3. Пересечение начала: Документ начался до начала периода, но закончился внутри него.
  4. Пересечение окончания: Документ начался внутри периода, а закончился уже после его завершения.

Рассмотрим, как реализовать это в языке запросов 1С.

Вариант 1. Универсальная математическая формула (рекомендуемый)

Самый изящный и производительный способ проверить пересечение двух интервалов [A, B] и [C, D] заключается в одном простом правиле: начало первого интервала должно быть меньше или равно конца второго, И конец первого интервала должен быть больше или равен началу второго.

В контексте нашей задачи условие в запросе будет выглядеть следующим образом:


ГДЕ
    Документ.ПланДатаНачала <= &КонДата
    И Документ.ПланДатаОкончания >= &НачДата

Разберем, почему это работает. Если ПланДатаНачала документа находится после &КонДата, значит, документ начнется уже после завершения интересующего нас периода. Если ПланДатаОкончания находится до &НачДата, значит, документ уже завершился к моменту начала периода. Инвертируя эти условия и соединяя их оператором И, мы получаем идеальный фильтр для всех четырех сценариев пересечения.

Вариант 2. Развернутое логическое условие

Если вам необходимо сделать код более «читаемым» для коллег или если бизнес-логика требует разного веса для разных типов пересечений, можно использовать конструкцию с оператором ИЛИ. Рассмотрим пример, который учитывает все условия в явном виде:


ГДЕ
    (Документ.ПланДатаНачала МЕЖДУ &НачДата И &КонДата)
    ИЛИ (Документ.ПланДатаОкончания МЕЖДУ &НачДата И &КонДата)
    ИЛИ (Документ.ПланДатаНачала <= &НачДата И Документ.ПланДатаОкончания >= &КонДата)

Проанализируем это условие по шагам:

  1. Первая строка проверяет, попадает ли точка начала документа в выбранный интервал.
  2. Вторая строка проверяет, попадает ли точка окончания документа в интервал.
  3. Третья строка обрабатывает случай «поглощения», когда сам документ начался раньше, а закончился позже периода, и ни одна из его границ не попадает внутрь МЕЖДУ.

Важно: Оператор МЕЖДУ в 1С включает границы периода. То есть Дата МЕЖДУ &НачДата И &КонДата эквивалентно Дата >= &НачДата И Дата <= &КонДата.

Особенности работы с временем и типами данных

При написании подобных запросов начинающие разработчики часто совершают ошибки, связанные со спецификой хранения дат и ссылочных типов. Рассмотрим важные нюансы.

1. Границы периода. Если вы используете СтандартныйПериод, помните, что ДатаОкончания должна включать последнюю секунду дня. В коде перед передачей параметра в запрос обязательно используйте функцию КонецДня() для конечной даты, иначе документы, запланированные на вторую половину последнего дня периода, не попадут в выборку.


Запрос.УстановитьПараметр("НачДата", ПериодНаФорме.ДатаНачала);
Запрос.УстановитьПараметр("КонДата", КонецДня(ПериодНаФорме.ДатаОкончания));

2. Составные типы данных. Если поле Документ в вашем регистре или таблице имеет составной тип, обращение через точку (Документ.ПланДатаНачала) приведет к существенному замедлению запроса. Система будет вынуждена выполнить LEFT JOIN со всеми таблицами документов, входящими в этот тип.

Проанализируем правильный подход: используйте функцию ВЫРАЗИТЬ, чтобы подсказать системе конкретную таблицу, или сначала выберите ссылки во временную таблицу.


ГДЕ
    ВЫРАЗИТЬ(СЦентр_ЗагрузкаМастеров.Документ КАК Документ.Выезд).ПланДатаНачала <= &КонДата
    И ВЫРАЗИТЬ(СЦентр_ЗагрузкаМастеров.Документ КАК Документ.Выезд).ПланДатаОкончания >= &НачДата

3. Пустые даты. Выясним, как ведет себя система, если ПланДатаОкончания не заполнена (равна 01.01.0001). В этом случае условие >= &НачДата не сработает. Если в вашей логике незаполненная дата окончания означает «бессрочно», добавьте проверку на пустую дату:


ГДЕ
    Документ.ПланДатаНачала <= &КонДата
    И (Документ.ПланДатаОкончания >= &НачДата ИЛИ Документ.ПланДатаОкончания = ДАТАВРЕМЯ(1, 1, 1))

Производительность и индексы

С точки зрения СУБД, вариант №1 (с оператором И) предпочтительнее варианта №2 (с оператором ИЛИ). При использовании ИЛИ оптимизатор базы данных часто отказывается от использования индексов и переходит к полному сканированию таблицы (Index Scan), что критично при больших объемах данных.

Для максимально быстрой работы запроса убедитесь, что поля ПланДатаНачала и ПланДатаОкончания индексированы в свойствах объекта конфигурации.

Подводя итог, можно сказать, что наиболее стабильным и лаконичным решением является проверка пересечения границ через два простых сравнения. Это покрывает все бизнес-кейсы и обеспечивает высокую скорость выполнения кода.

← На главную