Почему не работает отбор в Построителе запроса, если в колонке таблицы значений хранится список значений?

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

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

Анализ исходной ситуации

Рассмотрим пример кода, который часто вызывает затруднения. Представим, что у нас есть таблица клиентов, и у каждого клиента может быть несколько секторов (хранятся в СписокЗначений). Мы хотим отобрать только тех клиентов, чьи сектора входят в определенный набор.


// Сектора у клиента (в ячейке ТЗ)
СектораНабор1 = Новый СписокЗначений;
СектораНабор1.Добавить(1);
СектораНабор1.Добавить(2);

// Набор для фильтрации (что мы ищем)
СектораНаборДляОтбора = Новый СписокЗначений;
СектораНаборДляОтбора.Добавить(2);
СектораНаборДляОтбора.Добавить(3);

ТаблицаКлиентов = Новый ТаблицаЗначений;
ТаблицаКлиентов.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаКлиентов.Колонки.Добавить("Сектора", Новый ОписаниеТипов("СписокЗначений"));

НоваяСтрока = ТаблицаКлиентов.Добавить();
НоваяСтрока.Наименование = "Иван";
НоваяСтрока.Сектора = СектораНабор1;

Построитель = Новый ПостроительЗапроса;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаКлиентов);
Построитель.ДоступныеПоля.Сектора.Отбор = Истина;

НовыйОтбор = Построитель.Отбор.Добавить("Сектора");
НовыйОтбор.ВидСравнения = ВидСравнения.ВСписке;
НовыйОтбор.Значение = СектораНаборДляОтбора;
НовыйОтбор.Использование = Истина;

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

В данном сценарии ожидается, что «Иван» попадет в выборку, так как его сектор "2" есть в списке отбора. Однако ПостроительЗапроса выведет либо все строки, либо ни одной. Выясним причину такого поведения.

Почему стандартный механизм отбора не срабатывает?

Проблема заключается в том, как ОписаниеИсточникаДанных и механизмы сравнения платформы интерпретируют данные в ячейках. Проанализируем ключевые моменты:

  1. Объектная природа данных: Для системы 1С значение в колонке «Сектора» — это не набор чисел, а конкретный объект типа СписокЗначений (ссылка на область памяти). Когда вы устанавливаете ВидСравнения.ВСписке, система пытается проверить, равен ли весь объект целиком какому-либо элементу из СектораНаборДляОтбора. Естественно, объект списка не равен числу 2 или 3.
  2. Отсутствие итерации: Ни ПостроительЗапроса, ни СКД (Система компоновки данных) не умеют автоматически «раскрывать» вложенные коллекции внутри ячеек при выполнении условий ГДЕ. Они работают с типом данных колонки как с единым целым.
  3. Типизация в ОписанииИсточникаДанных: При использовании таблицы значений в качестве источника, сложные типы вроде списков часто определяются как «Произвольный тип». Операции сравнения (особенно «В списке») для произвольных типов на уровне запроса к ТЗ работают непредсказуемо или не работают вовсе.

Решение №1: Нормализация данных (Плоская таблица)

Это самый правильный и производительный метод с точки зрения архитектуры 1С. Чтобы отборы работали корректно, необходимо перевести таблицу из иерархического вида (один клиент — много секторов в списке) в «плоский» вид (один клиент + один сектор — одна строка).

Разберем по шагам, как трансформировать данные:


// Вместо хранения списка в одной строке:
// Иван | {1, 2}

// Создаем структуру:
// Иван | 1
// Иван | 2

ТаблицаДляПостроителя = Новый ТаблицаЗначений;
ТаблицаДляПостроителя.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ТаблицаДляПостроителя.Колонки.Добавить("Сектор", Новый ОписаниеТипов("Число")); // Простой тип!

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

Теперь, если мы назначим ТаблицаДляПостроителя источником данных, отбор по колонке «Сектор» с видом сравнения ВидСравнения.ВСписке отработает моментально и корректно, так как система будет сравнивать число с набором чисел.

Решение №2: Использование СКД и вложенных схем

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

Рассмотрим альтернативный вариант в СКД:

Решение №3: Ручной перебор (для небольших таблиц)

Если объем данных невелик и использование ПостроителяЗапроса не является жестким требованием интерфейса, можно воспользоваться методом НайтиСтроки() или простым циклом с проверкой. Посмотрим на пример функции, реализующей такой поиск:


Функция НайтиСтрокиПоВхождениюВСписок(ИсходнаяТЗ, СписокДляПоиска)
    РезультатТЗ = ИсходнаяТЗ.СкопироватьКолонки();
    
    Для Каждого СтрокаТЗ Из ИсходнаяТЗ Цикл
        Для Каждого ЗначениеИзЯчейки Из СтрокаТЗ.Сектора Цикл
            Если СписокДляПоиска.НайтиПоЗначению(ЗначениеИзЯчейки.Значение) <> Неопределено Тогда
                ЗаполнитьЗначенияСвойств(РезультатТЗ.Добавить(), СтрокаТЗ);
                Прервать; // Клиент уже подошел, переходим к следующему
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;
    
    Возврат РезультатТЗ;
КонецФункции

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

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

Важные рекомендации:

  1. Всегда нормализуйте данные перед передачей в ПостроительЗапроса или СКД, если планируется отбор по вложенным элементам.
  2. Помните, что ОписаниеИсточникаДанных эффективно работает только с примитивными типами (числа, строки, даты, ссылки).
  3. Индексация колонки, содержащей СписокЗначений, не ускорит поиск элементов внутри этого списка, так как индекс строится по ссылке на сам объект списка.

Соблюдение этих правил позволит вам избежать трудноуловимых ошибок в логике отчетов и обеспечит корректную работу фильтров в ваших программных продуктах на базе 1С.

← На главную