Как правильно настроить отбор по статусам заказов в запросе 1С и избежать типичных ошибок

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

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

Анализ проблемы: почему «исчезают» заказы?

Рассмотрим ситуацию, когда нам необходимо выбрать остатки по заказам покупателей, но при этом отфильтровать их по определенным статусам выполнения. Типичная ошибка начинающего разработчика выглядит так: используется ЛЕВОЕ СОЕДИНЕНИЕ с регистром статусов, а условие фильтрации (например, статус не равен «Выполнен») прописывается в секции ГДЕ.

Выясним причину проблемы: Механика работы SQL-сервера такова, что при использовании ЛЕВОЕ СОЕДИНЕНИЕ для записей основной таблицы, для которых не найдено соответствие в правой (присоединенной) таблице, во всех полях правой таблицы будет стоять значение NULL. Однако, как только мы добавляем условие в ГДЕ, например Статус.Наименование <> "Выполнен", система пытается сравнить NULL с заданной строкой. Любое сравнение с NULL (кроме специальных операторов проверки на пустоту) дает результат «Ложь». В итоге все заказы, у которых статус еще не установлен (записи в регистре сведений отсутствуют), просто выпадают из итоговой выборки. Таким образом, наше Левое соединение фактически превращается во Внутреннее.

Решение №1: Перенос условий в секцию связи ПО

Проанализируем самый простой и быстрый способ исправления этой логической ошибки. Чтобы сохранить все записи из левой таблицы (Заказы), условия фильтрации правой таблицы (Статусы) необходимо перенести непосредственно в условие соединения ПО.

Посмотрим на пример трансформации кода:


// Неправильно (теряем данные)
ИЗ РегистрНакопления.ЗаказыПокупателей.Остатки КАК ЗаказыОстатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыЗаказов КАК Статусы
ПО ЗаказыОстатки.Заказ = Статусы.Заказ
ГДЕ Статусы.Статус <> ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Выполнен)

// Правильно (сохраняем записи без статусов)
ИЗ РегистрНакопления.ЗаказыПокупателей.Остатки КАК ЗаказыОстатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтатусыЗаказов КАК Статусы
ПО ЗаказыОстатки.Заказ = Статусы.Заказ
   И Статусы.Статус <> ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Выполнен)

В этом случае условие будет применяться в момент соединения таблиц, и если статус заказа равен «Выполнен», соединение просто не произойдет (поля будут NULL), но сама строка заказа останется в результате.

Решение №2: Работа с виртуальными таблицами и срезом последних

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

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


ВЫБРАТЬ
    ЗаказыОстатки.Заказ КАК Заказ,
    ЗаказыОстатки.СуммаОстаток КАК Сумма,
    ЕСТЬNULL(СтатусыСрез.Статус, ЗНАЧЕНИЕ(Справочник.Статусы.ПустаяСсылка)) КАК ТекущийСтатус
ИЗ
    РегистрНакопления.ЗаказыПокупателей.Остатки(&Период, ) КАК ЗаказыОстатки
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрСведений.СтатусыВыполненияЗаказов.СрезПоследних(&Период, ) КАК СтатусыСрез
ПО 
    ЗаказыОстатки.Заказ = СтатусыСрез.Заказ
ГДЕ
    (СтатусыСрез.Статус.Наименование <> "Выполнен" ИЛИ СтатусыСрез.Статус ЕСТЬ NULL)

Обратите внимание на конструкцию ИЛИ СтатусыСрез.Статус ЕСТЬ NULL в секции ГДЕ. Это альтернативный способ решения проблемы «превращения» соединения во внутреннее, если вы все же решили оставить условие в нижней части запроса.

Решение №3: Отказ от сравнения по наименованию (строкам)

В исходном запросе часто встречается сравнение вида Статус.Наименование <> "Частично отгружен". Это крайне не рекомендуется в профессиональной разработке 1С по нескольким причинам:

  1. Производительность: Сравнение строковых полей в базе данных выполняется медленнее, чем сравнение ссылочных типов (Unique Identifier).
  2. Надежность: Если пользователь изменит название в справочнике или случайно добавит пробел в конце слова, ваш запрос перестанет работать, хотя логика программы не изменилась.
  3. Локализация: Если база будет использоваться на другом языке, строковые сравнения «сломаются».

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


// В коде 1С подготовим список нужных статусов
СписокСтатусов = Новый Массив;
СписокСтатусов.Добавить(Справочники.СтатусыЗаказов.НайтиПоНаименованию("Частично отгружен"));
СписокСтатусов.Добавить(Справочники.СтатусыЗаказов.НайтиПоНаименованию("Оформлен"));

Запрос.УстановитьПараметр("МассивСтатусов", СписокСтатусов);

// В тексте запроса используем оператор В
ГДЕ 
    СтатусыВыполненияЗаказов.Статус В (&МассивСтатусов)

Оптимизация: Разыменование полей и временные таблицы

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

Разберем по шагам, как оптимизировать такие обращения:

  1. Проверьте, нет ли нужного поля в измерениях регистра. Например, если Контрагент является измерением регистра ЗаказыПокупателей, обращайтесь к нему напрямую: ЗаказыПокупателейОстатки.Контрагент.
  2. Если нужное поле есть только в документе, вынесите список заказов во временную таблицу (ВТ), предварительно выбрав из документа все необходимые реквизиты одним запросом.
  3. Индексируйте поля связи во временных таблицах для ускорения последующих соединений.

Итоговый пример исправленного и оптимизированного запроса

Объединим все рекомендации и посмотрим на пример качественного запроса, который корректно обрабатывает остатки, статусы и исключает заказы, попавшие в определенные документы отгрузки:


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    Остатки.Заказ КАК ЗаказПокупателя,
    СУММА(ЕСТЬNULL(Остатки.СуммаОстаток, 0)) КАК Сумма,
    Остатки.Заказ.Контрагент КАК Контрагент,
    Остатки.Заказ.ДатаИсполнения КАК ДатаИсполнения
ПОМЕСТИТЬ ВТ_ОстаткиЗаказов
ИЗ
    РегистрНакопления.ЗаказыПокупателей.Остатки(&Период, Контрагент = &Контрагент) КАК Остатки
ГДЕ
    НЕ Остатки.Заказ.ПометкаУдаления
СГРУППИРОВАТЬ ПО
    Остатки.Заказ,
    Остатки.Заказ.Контрагент,
    Остатки.Заказ.ДатаИсполнения
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.ЗаказПокупателя,
    ВТ.Сумма,
    ВТ.Контрагент,
    ВТ.ДатаИсполнения
ИЗ
    ВТ_ОстаткиЗаказов КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрСведений.СтатусыВыполненияЗаказов.СрезПоследних(&Период, ) КАК Статусы
ПО 
    ВТ.ЗаказПокупателя = Статусы.Заказ
ГДЕ
    // Оставляем только те, что не выполнены или не имеют статуса
    (Статусы.Статус <> ЗНАЧЕНИЕ(Справочник.Статусы.Выполнен) ИЛИ Статусы.Статус ЕСТЬ NULL)
    И ВТ.ДатаИсполнения <= &СчетчикМесяца
    // Дополнительная проверка на отсутствие в других документах через ЕСТЬ NULL
    И НЕ ВТ.ЗаказПокупателя В (
        ВЫБРАТЬ Заказ ИЗ Документ.ПС_ЗаказыДляОтгрузки.Заказы ГДЕ Ссылка.ПометкаУдаления = ЛОЖЬ
    )

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

← На главную