Как в запросе 1С отобрать документы по периоду их действия (реквизиты ДатаНачала и ДатаОкончания)

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Проблема 1: Исчезновение документов из отчета («Нет движений — нет данных»)

Рассмотрим ситуацию, описанную автором темы. В информационной базе есть документ «Акция», у которого заданы реквизиты ДатаНачала и ДатаОкончания. Также существует регистр сведений АкционныеНачисления, куда записываются факты расхода по акциям. Автор пытается построить запрос напрямую к таблице регистра сведений:


ВЫБРАТЬ
    АкционныеНачисления.Акция.ДатаНачала,
    АкционныеНачисления.Акция.ДатаОкончания,
    АкционныеНачисления.Период
ИЗ
    РегистрСведений.АкционныеНачисления КАК АкционныеНачисления
ГДЕ
    АкционныеНачисления.Период МЕЖДУ &НачалоПериода И &КонецПериода

При таком подходе возникает серьезная логическая ошибка: если акция уже началась, но по ней еще не было зарегистрировано ни одного расхода (нет записей в регистре), то документ «Акция» вообще не попадет в отчет. Таблица регистра физически не содержит записей по новым или неиспользованным акциям.

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

Проанализируем базовый шаблон правильного выбора таблиц:


ВЫБРАТЬ
    Акции.Ссылка КАК Акция,
    Акции.ДатаНачала КАК ДатаНачала,
    Акции.ДатаОкончания КАК ДатаОкончания,
    ЕСТЬNULL(Начисления.РесурсРасход, 0) КАК Расход
ИЗ
    Документ.УстановкаСкидокНоменклатуры КАК Акции
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.АкционныеНачисления КАК Начисления
        ПО Начисления.Акция = Акции.Ссылка

При такой структуре запроса мы гарантированно получим список всех документов акций, а в колонках начислений отобразится NULL (который мы заменяем на 0 с помощью функции ЕСТЬNULL) для тех акций, по которым не было движений.

Проблема 2: Математический алгоритм пересечения двух интервалов

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

Разработчики часто совершают ошибку, пытаясь описать все возможные варианты пересечения через громоздкие условия с оператором ИЛИ (как показано в сообщениях 26 и 31 форума). Это усложняет чтение кода и замедляет выполнение SQL-запроса.

Давайте визуализируем четыре возможных сценария пересечения периода документа [ДН, ДО] и периода отчета [ПН, ПО]:

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

Существует простая и элегантная математическая формула, которая покрывает все четыре сценария одновременно. Два интервала пересекаются тогда и только тогда, когда начало первого интервала меньше или равно концу второго, а конец первого интервала больше или равен началу второго.

В терминах СУБД и языка запросов 1С это условие записывается всего в две строки:


ГДЕ
    Акции.ДатаНачала <= &КонецПериода
    И Акции.ДатаОкончания >= &НачалоПериода

Давайте проверим эту формулу на примере из сообщения 19: у документа дата начала 05.11.2019, дата окончания 10.11.2019. В отчете мы задаем период с 06.11.2019 по 07.11.2019.

Подставим значения в формулу:

Оба условия выполняются, документ успешно попадает в выборку!

Проблема 3: Обработка бессрочных акций (пустая дата окончания)

В реальных конфигурациях (например, в «Управление торговлей 10.3» или «Розница») маркетинговые акции часто создаются без указания даты окончания. Это означает, что акция действует бессрочно. В базе данных незаполненная дата хранится как пустая дата 1С — 01.01.0001 00:00:00 (или пустая дата СУБД).

Если мы применим наше универсальное условие пересечения к бессрочной акции, то проверка Акции.ДатаОкончания >= &НачалоПериода вернет Ложь, так как пустая дата 01.01.0001 всегда меньше любой реальной даты отчета. В результате все бессрочные акции исчезнут из отчета.

Чтобы избежать этой ошибки, мы должны доработать условие в секции ГДЕ, добавив проверку на пустую дату окончания:


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

Проблема 4: Производительность запроса и разыменование ссылок

Обратим внимание на конструкцию АкционныеНачисления.Акция.ДатаНачала из первого сообщения форума. Обращение к реквизитам через точку от ссылочного поля (разыменование ссылки) на уровне СУБД приводит к неявному соединению таблиц.

Если поле Акция имеет составной тип данных, платформа 1С сгенерирует сложный SQL-запрос с левым соединением ко всем таблицам документов, входящим в этот тип. На больших базах данных это приводит к колоссальному падению производительности и неоптимальным планам запросов.

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

Пошаговая сборка оптимального запроса

С учетом всех разобранных правил и нюансов, составим финальный, оптимизированный и отказоустойчивый запрос для получения акций за период:


ВЫБРАТЬ
    ДокументАкции.Ссылка КАК ДокументАкция,
    ДокументАкции.ДатаНачала КАК ДатаНачала,
    ДокументАкции.ДатаОкончания КАК ДатаОкончания,
    ЕСТЬNULL(РегистрНачисления.Период, ДАТАВРЕМЯ(1, 1, 1)) КАК ПериодРасхода,
    ЕСТЬNULL(РегистрНачисления.СуммаРасход, 0) КАК СуммаРасхода
ИЗ
    Документ.УстановкаСкидокНоменклатуры КАК ДокументАкции
    
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.АкционныеНачисления КАК РегистрНачисления
        ПО РегистрНачисления.Акция = ДокументАкции.Ссылка

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

Разберем детально, что мы сделали в этом запросе:

  1. Выбрали основную таблицу Документ.УстановкаСкидокНоменклатуры, чтобы гарантировать вывод всех документов, даже если по ним нет записей в регистре.
  2. Применили ЛЕВОЕ СОЕДИНЕНИЕ с таблицей регистра сведений по ссылке документа.
  3. Обернули потенциально пустые значения из левой таблицы в функцию ЕСТЬNULL для корректного вывода нулей и пустых дат вместо неопределенных значений СУБД.
  4. Применили функции НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА к параметрам запроса, чтобы исключить влияние времени (часы, минуты, секунды) на корректность отбора.
  5. Использовали математически точное условие пересечения временных интервалов, устойчивое к неполным периодам акций.
  6. Учли возможность существования «бессрочных» акций с пустой датой окончания действия.

Используя данный подход, вы сможете быстро и без потери производительности системы строить любые отчеты, анализирующие работу периодических документов в системе 1С:Предприятие.

← На главную