Почему динамический список 1С фильтрует данные только по первому элементу из списка складов?

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

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

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

Анализ исходной ситуации и поиск причины

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


ВЫБРАТЬ
    ОстаткиТоваров.Склад,
    ОстаткиТоваров.Номенклатура,
    ОстаткиТоваров.В НаличииОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки КАК ОстаткиТоваров
ГДЕ
    ОстаткиТоваров.Склад В (&СписокСкладов)

При открытии формы разработчик заполняет параметр следующим образом:


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

Проанализируем, что происходит в этот момент. В консоли запросов объект СписокЗначений преобразуется в массив для SQL-инструкции IN. Однако динамический список базируется на технологии Системы компоновки данных (СКД). Движок СКД при обработке параметров иногда проявляет избыточную "интеллектуальность". Если тип параметра в схеме жестко не определен как массив, платформа может проанализировать первый элемент СписокЗначений (например, ссылку на справочник Склады) и ошибочно типизировать весь параметр как одиночную ссылку. В итоге условие В (&СписокСкладов) превращается в Склад = &ОдинСклад.

Решение №1: Использование массива вместо списка значений

Самый простой и эффективный способ решить проблему — заменить СписокЗначений на Массив. Массив является более "чистой" структурой данных для SQL-запроса, так как он не несет в себе интерфейсных свойств (пометки, картинки, представления), которые есть у СписокЗначений. Посмотрим на исправленный вариант кода, использующий метод ВыгрузитьКолонку(), который сразу возвращает массив:


// Вместо цикла и создания СпискаЗначений используем выгрузку колонки
ДоступныеСкладыМассив = ПараметрыСеанса.ТекущийПользователь.ДоступныеСклады.ВыгрузитьКолонку("Склад");

// Устанавливаем массив в параметры динамического списка
ДинамическийСписок.Параметры.УстановитьЗначениеПараметра("СписокСкладов", ДоступныеСкладыМассив);

Выясним причину, почему это работает: когда платформа получает Массив, у нее не остается сомнений в том, что параметр является коллекцией. Это исключает неверную типизацию по первому элементу, и условие В (оператор IN в SQL) отрабатывает корректно для всех элементов коллекции.

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

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

Правильный подход — передавать список складов непосредственно в параметры виртуальной таблицы. Исправим текст запроса в динамическом списке:


ВЫБРАТЬ
    ОстаткиТоваров.Склад,
    ОстаткиТоваров.Номенклатура,
    ОстаткиТоваров.КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад В (&СписокСкладов)) КАК ОстаткиТоваров

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

Решение №3: Использование встроенных отборов динамического списка

Разберем еще один важный нюанс. Динамический список спроектирован так, чтобы пользователь или программист могли гибко управлять фильтрами без изменения текста запроса. Вместо жесткого прописывания параметра &СписокСкладов в тексте, часто лучше использовать механизм отборов.

Выполним следующие шаги:

  1. Уберем условие ГДЕ или параметры из текста запроса.
  2. Установим отбор программно через КомпоновщикНастроек:

// Очистим старые отборы по полю Склад, если они были
ПолеСклад = Новый ПолеКомпоновкиДанных("Склад");
Для Каждого ЭлементОтбора Из Список.Отбор.Элементы Цикл
    Если ЭлементОтбора.ЛевоеЗначение = ПолеСклад Тогда
        Список.Отбор.Элементы.Удалить(ЭлементОтбора);
    КонецЕсли;
КонецЦикла;

// Добавим новый отбор с видом сравнения "В списке"
НовыйОтбор = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
НовыйОтбор.ЛевоеЗначение = ПолеСклад;
НовыйОтбор.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
НовыйОтбор.ПравоеЗначение = ДоступныеСкладыМассив; // Используем наш массив
НовыйОтбор.Использование = Истина;

Важное преимущество этого метода: СКД динамического списка самостоятельно внедрит это условие в наиболее оптимальное место запроса (в том числе в параметры виртуальных таблиц), обеспечив максимальную производительность без "ручного" вмешательства в SQL-код.

Подведем итоги

Проанализировав поведение платформы, мы пришли к следующим выводам:

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

← На главную