При разработке сложных отчетов или обработок на платформе 1С:Предприятие программисты часто сталкиваются с необходимостью фильтрации данных во временных таблицах или таблицах значений (ТЗ). Типичная ситуация: у нас есть таблица, где в одной из колонок хранится не простое значение (строка или число), а целая коллекция — например, СписокЗначений. Попытка настроить отбор через ПостроительЗапроса по такой колонке с использованием вида сравнения ВСписке часто приводит к тому, что фильтр просто игнорируется или выдает пустой результат. Разберем подробно, почему так происходит и как решить эту задачу правильно.
Рассмотрим пример кода, который часто вызывает затруднения. Представим, что у нас есть таблица клиентов, и у каждого клиента может быть несколько секторов (хранятся в СписокЗначений). Мы хотим отобрать только тех клиентов, чьи сектора входят в определенный набор.
// Сектора у клиента (в ячейке ТЗ)
СектораНабор1 = Новый СписокЗначений;
СектораНабор1.Добавить(1);
СектораНабор1.Добавить(2);
// Набор для фильтрации (что мы ищем)
СектораНаборДляОтбора = Новый СписокЗначений;
СектораНаборДляОтбора.Добавить(2);
СектораНаборДляОтбора.Добавить(3);
ТаблицаКлиентов = Новый ТаблицаЗначений;
ТаблицаКлиентов.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаКлиентов.Колонки.Добавить("Сектора", Новый ОписаниеТипов("СписокЗначений"));
НоваяСтрока = ТаблицаКлиентов.Добавить();
НоваяСтрока.Наименование = "Иван";
НоваяСтрока.Сектора = СектораНабор1;
Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаКлиентов);
Построитель.ДоступныеПоля.Сектора.Отбор = Истина;
НовыйОтбор = Построитель.Отбор.Добавить("Сектора");
НовыйОтбор.ВидСравнения = ВидСравнения.ВСписке;
НовыйОтбор.Значение = СектораНаборДляОтбора;
НовыйОтбор.Использование = Истина;
Построитель.Выполнить();
ТаблицаРезультат = Построитель.Результат.Выгрузить();
В данном сценарии ожидается, что «Иван» попадет в выборку, так как его сектор "2" есть в списке отбора. Однако ПостроительЗапроса выведет либо все строки, либо ни одной. Выясним причину такого поведения.
Проблема заключается в том, как ОписаниеИсточникаДанных и механизмы сравнения платформы интерпретируют данные в ячейках. Проанализируем ключевые моменты:
СписокЗначений (ссылка на область памяти). Когда вы устанавливаете ВидСравнения.ВСписке, система пытается проверить, равен ли весь объект целиком какому-либо элементу из СектораНаборДляОтбора. Естественно, объект списка не равен числу 2 или 3.ПостроительЗапроса, ни СКД (Система компоновки данных) не умеют автоматически «раскрывать» вложенные коллекции внутри ячеек при выполнении условий ГДЕ. Они работают с типом данных колонки как с единым целым.Это самый правильный и производительный метод с точки зрения архитектуры 1С. Чтобы отборы работали корректно, необходимо перевести таблицу из иерархического вида (один клиент — много секторов в списке) в «плоский» вид (один клиент + один сектор — одна строка).
Разберем по шагам, как трансформировать данные:
// Вместо хранения списка в одной строке:
// Иван | {1, 2}
// Создаем структуру:
// Иван | 1
// Иван | 2
ТаблицаДляПостроителя = Новый ТаблицаЗначений;
ТаблицаДляПостроителя.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаДляПостроителя.Колонки.Добавить("Сектор", Новый ОписаниеТипов("Число")); // Простой тип!
Для Каждого СтрокаИзИсходнойТЗ Из ТаблицаКлиентов Цикл
Для Каждого ЭлементСектора Из СтрокаИзИсходнойТЗ.Сектора Цикл
НоваяСтрока = ТаблицаДляПостроителя.Добавить();
НоваяСтрока.Наименование = СтрокаИзИсходнойТЗ.Наименование;
НоваяСтрока.Сектор = ЭлементСектора.Значение;
КонецЦикла;
КонецЦикла;
Теперь, если мы назначим ТаблицаДляПостроителя источником данных, отбор по колонке «Сектор» с видом сравнения ВидСравнения.ВСписке отработает моментально и корректно, так как система будет сравнивать число с набором чисел.
Если вы используете Систему компоновки данных, можно попытаться обойти ограничение, не разворачивая таблицу вручную в коде, а используя особенности вычисляемых полей или соединений. Однако, даже в СКД, прямое сравнение списка в ячейке с другим списком в параметрах не даст результата «пересечения множеств».
Рассмотрим альтернативный вариант в СКД:
Представление() или преобразуйте список в строку.Если объем данных невелик и использование ПостроителяЗапроса не является жестким требованием интерфейса, можно воспользоваться методом НайтиСтроки() или простым циклом с проверкой. Посмотрим на пример функции, реализующей такой поиск:
Функция НайтиСтрокиПоВхождениюВСписок(ИсходнаяТЗ, СписокДляПоиска)
РезультатТЗ = ИсходнаяТЗ.СкопироватьКолонки();
Для Каждого СтрокаТЗ Из ИсходнаяТЗ Цикл
Для Каждого ЗначениеИзЯчейки Из СтрокаТЗ.Сектора Цикл
Если СписокДляПоиска.НайтиПоЗначению(ЗначениеИзЯчейки.Значение) <> Неопределено Тогда
ЗаполнитьЗначенияСвойств(РезультатТЗ.Добавить(), СтрокаТЗ);
Прервать; // Клиент уже подошел, переходим к следующему
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат РезультатТЗ;
КонецФункции
Проанализировав ситуацию, мы выяснили, что ПостроительЗапроса — это мощный инструмент, но он жестко следует правилам типизации данных. Хранение коллекций (Массив, СписокЗначений) внутри ячеек таблиц допустимо для хранения или отображения, но непригодно для стандартных механизмов фильтрации.
Важные рекомендации:
ПостроительЗапроса или СКД, если планируется отбор по вложенным элементам.ОписаниеИсточникаДанных эффективно работает только с примитивными типами (числа, строки, даты, ссылки).СписокЗначений, не ускорит поиск элементов внутри этого списка, так как индекс строится по ссылке на сам объект списка.Соблюдение этих правил позволит вам избежать трудноуловимых ошибок в логике отчетов и обеспечит корректную работу фильтров в ваших программных продуктах на базе 1С.