В практике разработки на платформе 1С часто встречается ситуация, когда запрос, идеально работающий в консоли запросов, начинает вести себя непредсказуемо внутри динамического списка. Типичный пример: фильтрация остатков по списку складов. Программист передает в параметр запроса СписокЗначений, содержащий несколько элементов, но список отображает данные только по одному складу — тому, который идет первым в коллекции. При этом отладчик подтверждает, что в списке значений действительно несколько элементов, а консоль запросов при тех же условиях выдает корректный результат.
Разберем детально, почему это происходит, как работает механизм динамического списка "под капотом" и как правильно передавать коллекции значений в параметры запроса, чтобы избежать подобных ошибок.
Рассмотрим код, который часто приводит к ошибке. Предположим, у нас есть запрос к виртуальной таблице остатков регистра накопления:
ВЫБРАТЬ
ОстаткиТоваров.Склад,
ОстаткиТоваров.Номенклатура,
ОстаткиТоваров.В НаличииОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки КАК ОстаткиТоваров
ГДЕ
ОстаткиТоваров.Склад В (&СписокСкладов)
При открытии формы разработчик заполняет параметр следующим образом:
СписокСкладов = Новый СписокЗначений();
Для Каждого СтрСкл Из ПараметрыСеанса.ТекущийПользователь.ДоступныеСклады Цикл
СписокСкладов.Добавить(СтрСкл.Склад);
КонецЦикла;
ДинамическийСписок.Параметры.УстановитьЗначениеПараметра("СписокСкладов", СписокСкладов);
Проанализируем, что происходит в этот момент. В консоли запросов объект СписокЗначений преобразуется в массив для SQL-инструкции IN. Однако динамический список базируется на технологии Системы компоновки данных (СКД). Движок СКД при обработке параметров иногда проявляет избыточную "интеллектуальность". Если тип параметра в схеме жестко не определен как массив, платформа может проанализировать первый элемент СписокЗначений (например, ссылку на справочник Склады) и ошибочно типизировать весь параметр как одиночную ссылку. В итоге условие В (&СписокСкладов) превращается в Склад = &ОдинСклад.
Самый простой и эффективный способ решить проблему — заменить СписокЗначений на Массив. Массив является более "чистой" структурой данных для SQL-запроса, так как он не несет в себе интерфейсных свойств (пометки, картинки, представления), которые есть у СписокЗначений.
Посмотрим на исправленный вариант кода, использующий метод ВыгрузитьКолонку(), который сразу возвращает массив:
// Вместо цикла и создания СпискаЗначений используем выгрузку колонки
ДоступныеСкладыМассив = ПараметрыСеанса.ТекущийПользователь.ДоступныеСклады.ВыгрузитьКолонку("Склад");
// Устанавливаем массив в параметры динамического списка
ДинамическийСписок.Параметры.УстановитьЗначениеПараметра("СписокСкладов", ДоступныеСкладыМассив);
Выясним причину, почему это работает: когда платформа получает Массив, у нее не остается сомнений в том, что параметр является коллекцией. Это исключает неверную типизацию по первому элементу, и условие В (оператор IN в SQL) отрабатывает корректно для всех элементов коллекции.
Рассмотрим ситуацию с точки зрения производительности. В исходном коде условие ГДЕ наложено на результат выборки из виртуальной таблицы. Проанализируем, к чему это приводит: СУБД сначала может попытаться рассчитать остатки по всем складам в базе данных, и только потом отфильтровать нужные. Это нерационально.
Правильный подход — передавать список складов непосредственно в параметры виртуальной таблицы. Исправим текст запроса в динамическом списке:
ВЫБРАТЬ
ОстаткиТоваров.Склад,
ОстаткиТоваров.Номенклатура,
ОстаткиТоваров.КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад В (&СписокСкладов)) КАК ОстаткиТоваров
В этом случае фильтрация произойдет на уровне получения данных из итогов регистра, что значительно ускорит работу динамического списка, особенно при большом объеме данных.
Разберем еще один важный нюанс. Динамический список спроектирован так, чтобы пользователь или программист могли гибко управлять фильтрами без изменения текста запроса. Вместо жесткого прописывания параметра &СписокСкладов в тексте, часто лучше использовать механизм отборов.
Выполним следующие шаги:
ГДЕ или параметры из текста запроса.КомпоновщикНастроек:
// Очистим старые отборы по полю Склад, если они были
ПолеСклад = Новый ПолеКомпоновкиДанных("Склад");
Для Каждого ЭлементОтбора Из Список.Отбор.Элементы Цикл
Если ЭлементОтбора.ЛевоеЗначение = ПолеСклад Тогда
Список.Отбор.Элементы.Удалить(ЭлементОтбора);
КонецЕсли;
КонецЦикла;
// Добавим новый отбор с видом сравнения "В списке"
НовыйОтбор = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
НовыйОтбор.ЛевоеЗначение = ПолеСклад;
НовыйОтбор.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
НовыйОтбор.ПравоеЗначение = ДоступныеСкладыМассив; // Используем наш массив
НовыйОтбор.Использование = Истина;
Важное преимущество этого метода: СКД динамического списка самостоятельно внедрит это условие в наиболее оптимальное место запроса (в том числе в параметры виртуальных таблиц), обеспечив максимальную производительность без "ручного" вмешательства в SQL-код.
Проанализировав поведение платформы, мы пришли к следующим выводам:
СписокЗначений как параметр запроса, ограничиваясь только первым элементом.Массив решает проблему типизации и гарантирует корректную работу оператора В.ГДЕ.КомпоновщикНастроек) является наиболее "родным" и гибким способом фильтрации данных в динамических списках.Рассмотренные шаги помогут вам создавать быстрые и отказоустойчивые интерфейсы, в которых данные всегда соответствуют ожиданиям пользователя.