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

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

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

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

Вот первоначальный код:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ДоговорыКонтрагентовУслуги.Номенклатура КАК Номенклатура,
    |    ДоговорыКонтрагентовУслуги.Цена КАК Цена,
    |    ДоговорыКонтрагентовУслуги.Сумма КАК Сумма,
    |    ДоговорыКонтрагентовУслуги.Количество КАК Количество,
    |    ДоговорыКонтрагентовУслуги.Ссылка.Владелец КАК Контрагент,
    |    ДоговорыКонтрагентовУслуги.Ссылка.Ссылка КАК Договор
    |ИЗ
    |    Справочник.ДоговорыКонтрагентов.Услуги КАК ДоговорыКонтрагентовУслуги
    |ГДЕ
    |    ДоговорыКонтрагентовУслуги.Ссылка.Владелец В (&МассивКонтрагентов)";

Если МассивКонтрагентов <> Неопределено Тогда
    Запрос.УстановитьПараметр("МассивКонтрагентов", МассивКонтрагентов);
КонецЕсли;

ППТабЗнач = Запрос.Выполнить().Выгрузить();

Ошибка "Не установлен параметр &МассивКонтрагентов" возникает потому, что если переменная МассивКонтрагентов равна Неопределено, условие Если не выполняется. В результате метод Запрос.УстановитьПараметр() не вызывается, но в тексте запроса все еще присутствует обращение к параметру &МассивКонтрагентов. Платформа 1С не знает, какое значение подставить, и прерывает выполнение с ошибкой.

Решение 1: Динамическое изменение текста запроса

Один из самых простых и наглядных способов — изменять сам текст запроса в зависимости от условий. Если отбор не нужен, мы можем либо полностью убрать секцию ГДЕ, либо заменить условие на такое, которое всегда будет истинным.

Вариант с заменой подстроки:

Модифицируем код так, чтобы при отсутствии массива контрагентов условие отбора заменялось на ИСТИНА. Условие ГДЕ ИСТИНА равносильно отсутствию условия.

  1. Проверяем, заполнено ли значение для отбора.
  2. Если заполнено, устанавливаем параметр.
  3. Если не заполнено, находим в тексте запроса наше условие и заменяем его на "ИСТИНА".

// Исходный текст запроса тот же, с условием ГДЕ
...

Если ЗначениеЗаполнено(МассивКонтрагентов) Тогда
    // Если массив не пустой, устанавливаем параметр
    Запрос.УстановитьПараметр("МассивКонтрагентов", МассивКонтрагентов);
Иначе
    // Если отбор не нужен, заменяем условие на всегда истинное
    Запрос.Текст = СтрЗаменить(Запрос.Текст, 
        "ДоговорыКонтрагентовУслуги.Ссылка.Владелец В (&МассивКонтрагентов)", 
        "ИСТИНА");
КонецЕсли;

Результат = Запрос.Выполнить().Выгрузить();

Важно: Обратите внимание, что слово ИСТИНА передается в функцию СтрЗаменить как строка с кавычками внутри: "ИСТИНА", либо без них, как в примере выше. В языке запросов ИСТИНА — это ключевое слово, а не строковый литерал 'Да'. Первоначально в обсуждении была допущена ошибка, которая приводила к сообщению "Поле не найдено "Да"".

Более надежный вариант с конкатенацией (сборкой) текста:

Метод СтрЗаменить может быть ненадежен, если в запросе есть похожие подстроки. Более правильным подходом считается "сборка" текста запроса из частей.


ТекстЗапроса = 
    "ВЫБРАТЬ
    |    ДоговорыКонтрагентовУслуги.Номенклатура,
    |    ДоговорыКонтрагентовУслуги.Цена,
    |    ДоговорыКонтрагентовУслуги.Сумма,
    |    ДоговорыКонтрагентовУслуги.Количество,
    |    ДоговорыКонтрагентовУслуги.Ссылка.Владелец КАК Контрагент,
    |    ДоговорыКонтрагентовУслуги.Ссылка.Ссылка КАК Договор
    |ИЗ
    |    Справочник.ДоговорыКонтрагентов.Услуги КАК ДоговорыКонтрагентовУслуги";

Запрос = Новый Запрос;

// Используем функцию ЗначениеЗаполнено(), она корректно обработает и Неопределено,
// и пустые массивы, и пустые ссылки.
Если ЗначениеЗаполнено(МассивКонтрагентов) Тогда
    // Если массив для отбора есть, добавляем к запросу секцию ГДЕ
    ТекстЗапроса = ТекстЗапроса + "
    |ГДЕ
    |    ДоговорыКонтрагентовУслуги.Ссылка.Владелец В (&МассивКонтрагентов)";
    Запрос.УстановитьПараметр("МассивКонтрагентов", МассивКонтрагентов);
КонецЕсли;

Запрос.Текст = ТекстЗапроса;
Результат = Запрос.Выполнить().Выгрузить();

Решение 2: Использование конструкций языка запросов

Можно обойтись без манипуляций с текстом запроса, используя всю мощь самого языка. Конструкция ВЫБОР ... КОГДА ... ИНАЧЕ позволяет реализовать условную логику прямо внутри запроса.

Для этого нам понадобится дополнительный параметр-флаг, который будет сообщать запросу, нужно ли применять отбор.

  1. В секции ГДЕ пишем конструкцию ВЫБОР.
  2. Если флаг (например, &ПрименятьОтбор) равен ИСТИНА, то проверяем основное условие Поле В (&Массив).
  3. Если флаг равен ЛОЖЬ, то возвращаем ИСТИНА, что делает условие неактивным для текущей записи.

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ДоговорыКонтрагентовУслуги.Номенклатура,
    |    ...
    |ИЗ
    |    Справочник.ДоговорыКонтрагентов.Услуги КАК ДоговорыКонтрагентовУслуги
    |ГДЕ
    |    ВЫБОР
    |        КОГДА &ПрименятьОтбор
    |            ТОГДА ДоговорыКонтрагентовУслуги.Ссылка.Владелец В (&МассивКонтрагентов)
    |        ИНАЧЕ ИСТИНА
    |    КОНЕЦ";

// Определяем, нужно ли применять отбор
ПрименятьОтбор = ЗначениеЗаполнено(МассивКонтрагентов);
Запрос.УстановитьПараметр("ПрименятьОтбор", ПрименятьОтбор);

// Важно! Параметр &МассивКонтрагентов нужно установить в любом случае,
// даже если он не будет использоваться, т.к. он присутствует в тексте запроса.
// Если отбор не применяется, можно передать Неопределено или пустой массив.
Если ПрименятьОтбор Тогда
    Запрос.УстановитьПараметр("МассивКонтрагентов", МассивКонтрагентов);
Иначе
    Запрос.УстановитьПараметр("МассивКонтрагентов", Неопределено);
КонецЕсли;

Результат = Запрос.Выполнить().Выгрузить();

Этот способ элегантен тем, что текст запроса остается неизменным, меняются только значения параметров. Это может быть полезно при работе со сложными запросами или при их хранении в макетах.

Важный нюанс: `Неопределено` или пустой массив?

В ходе обсуждения была поднята очень важная тема: разница между переменной, равной Неопределено, и переменной, содержащей пустой массив.

Проверка Если МассивКонтрагентов <> Неопределено для пустого массива вернет ИСТИНА. Если после такой проверки установить параметр и выполнить запрос с условием ... В (&ПустойМассив), то запрос не вернет ни одной строки, так как ни одно значение не может входить в пустой список.

Это частая причина, по которой "отбор не работает" и возвращается пустая таблица. Правильная проверка должна учитывать не только факт существования массива, но и наличие в нем элементов.

Рекомендуемые проверки:

  1. Если МассивКонтрагентов.Количество() > 0 Тогда — самая точная проверка, если вы уверены, что переменная всегда содержит объект типа "Массив" (или другую коллекцию с методом Количество()).
  2. Если ЗначениеЗаполнено(МассивКонтрагентов) Тогда — универсальная функция платформы. Она возвращает ЛОЖЬ для Неопределено, NULL, пустых строк, числового 0, пустых ссылок и пустых коллекций (массивов, списков, таблиц значений). Это самый надежный способ проверить, нужно ли применять отбор.

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

← На главную