В практике разработки на платформе 1С:Предприятие часто возникает задача: реализовать фильтр в отчете или обработке так, чтобы при выбранных значениях (например, конкретных складах) данные фильтровались, а при пустом списке — выводились все записи без исключения. Попытка решить это "в лоб" внутри языка запросов часто приводит к ошибкам типов или неоптимальной работе системы. Рассмотрим подробно, как правильно реализовать эту логику разными способами.
Разберем типичную ошибку начинающего разработчика. Часто пытаются написать условие так: ВЫБОР КОГДА &Склад <> ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка) ТОГДА Склад В (&Склад) ИНАЧЕ ИСТИНА КОНЕЦ. Проблема здесь в том, что параметр &Склад в данном контексте является коллекцией (Массив или СписокЗначений), а ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка) — это одиночная ссылка. Система компоновки или SQL-сервер не могут сравнить массив с одиночным значением, что вызывает ошибку несовместимости типов.
Проанализируем ситуацию: нам нужно, чтобы условие ГДЕ либо ограничивало выборку, либо становилось "прозрачным" (всегда истинным), если отбор не задан. Рассмотрим самые эффективные методы решения.
Этот способ считается классическим и наиболее понятным. Мы заранее определяем на стороне встроенного языка, заполнен ли наш список, и передаем в запрос логический флаг (Булево). Рассмотрим пример реализации (для быстрой подготовки кода можно использовать Консоль кода с ИИ-помощником):
// Определяем, заданы ли склады для фильтрации
СкладыЗаданы = (СписокСкладов.Количество() > 0);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура,
| Продажи.Количество
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| ВЫБОР
| КОГДА &СкладыЗаданы ТОГДА Продажи.Склад В (&СписокСкладов)
| ИНАЧЕ ИСТИНА
| КОНЕЦ";
Запрос.УстановитьПараметр("СкладыЗаданы", СкладыЗаданы);
Запрос.УстановитьПараметр("СписокСкладов", СписокСкладов);
Результат = Запрос.Выполнить();
Преимущества этого метода: простота написания и стабильность (для быстрой отладки подойдет консоль запросов для управляемых форм). Мы явно управляем логикой запроса. Недостатки: на очень больших таблицах конструкция ВЫБОР в блоке ГДЕ может помешать оптимизатору СУБД использовать индексы (запрос перестанет быть SARGable), что приведет к замедлению работы.
Проанализируем более профессиональный подход, который используют опытные разработчики для оптимизации производительности. Мы будем изменять сам текст запроса в зависимости от того, заполнен ли параметр. Если список пуст, мы просто не будем добавлять условие ГДЕ.
ТекстЗапроса =
"ВЫБРАТЬ
| СвободныеОстатки.Номенклатура,
| СвободныеОстатки.В Наличии
|ИЗ
| РегистрНакопления.СвободныеОстатки КАК СвободныеОстатки
|{ГДЕ_УСЛОВИЕ}";
Если СписокСкладов.Количество() > 0 Тогда
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "{ГДЕ_УСЛОВИЕ}", "ГДЕ СвободныеОстатки.Склад В (&СписокСкладов)");
Запрос.УстановитьПараметр("СписокСкладов", СписокСкладов);
Иначе
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "{ГДЕ_УСЛОВИЕ}", "");
КонецЕсли;
Запрос.Текст = ТекстЗапроса;
Выясним причину, по которой этот метод лучше: СУБД получает "чистый" SQL-запрос. Если склады не выбраны, в запросе вообще нет лишних проверок, и система просто читает данные. Если склады выбраны, используется стандартный оператор IN, который отлично оптимизируется индексами.
Рассмотрим вариант, когда платформа сама берет на себя управление фильтрами. Если вы используете Систему компоновки данных (СКД), вам вообще не нужно писать условия "если пусто — всё". Достаточно добавить поле в "Отбор" или сделать параметр необязательным. Платформа сама проанализирует настройки пользователя и, если галочка напротив склада не стоит, исключит это условие из итогового SQL-запроса.
Если же вы работаете в коде, можно использовать ПостроительЗапроса. Посмотрим на пример:
Построитель = Новый ПостроительЗапроса;
Построитель.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.Склад КАК Склад
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
|{ГДЕ
| Остатки.Склад.* КАК Склад}";
// Если мы НЕ добавим отбор программно, запрос выберет ВСЕ склады
Если СписокСкладов.Количество() > 0 Тогда
НовыйОтбор = Построитель.Отбор.Добавить("Склад");
НовыйОтбор.ВидСравнения = ВидСравнения.ВСписке;
НовыйОтбор.Значение = СписокСкладов;
НовыйОтбор.Использование = Истина;
КонецЕсли;
Построитель.Выполнить();
Результат = Построитель.Результат;
Символы {} в тексте запроса указывают построителю (или СКД) на то, что этот блок является необязательным. Это наиболее "правильный" с точки зрения архитектуры 1С способ.
Проанализируем еще несколько нюансов, которые помогут избежать ошибок при работе с большими объемами данных:
В (&Список) в SQL имеет свои пределы (обычно около 1000–2000 элементов). Если ваш список складов или товаров может содержать десятки тысяч позиций, запрос может завершиться аварийно. В таких случаях лучше использовать временные таблицы.
МенеджерВТ = Новый МенеджерВременныхТаблиц;
ЗапросВТ = Новый Запрос("ВЫБРАТЬ Ссылка ПОМЕСТИТЬ ВТ_Склады ИЗ &МассивСкладов");
ЗапросВТ.МенеджерВременныхТаблиц = МенеджерВТ;
ЗапросВТ.УстановитьПараметр("МассивСкладов", СписокСкладов);
ЗапросВТ.Выполнить();
ГДЕ (НЕ &ИспользоватьОтбор) ИЛИ (Склад В (&Список)). Она работает, но часто вызывает "сканирование таблицы" (Table Scan) вместо "поиска по индексу" (Index Seek), что существенно замедляет отчет на больших базах.Подведем итог: для простых задач и небольших таблиц идеально подходит Метод 1 (Флаг в запросе). Если же вы разрабатываете высоконагруженную систему или сложный отчет, лучше всего использовать Метод 2 (Динамический текст) или стандартные механизмы СКД.