Как выполнить отбор в таблице значений по колонке с типом СписокЗначений?

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

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

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

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

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

Когда мы передаем ТаблицуЗначений в запрос, система должна типизировать каждую колонку. Типы СписокЗначений или Массив являются сложными объектами 1С. Для движка запросов содержимое такой ячейки — это не набор значений, а ссылка на объект в оперативной памяти (или массив байтов). Оператор В (IN) в языке запросов предназначен для проверки вхождения одиночного значения (числа, строки, ссылки) в плоский массив. Он не умеет "заглядывать" внутрь объекта СписокЗначений, лежащего в ячейке, и проверять пересечение элементов.

Поэтому нам необходимо реализовать логику отбора программными методами или изменить структуру данных. Рассмотрим доступные варианты.

Способ 1. Использование цикла и метода НайтиСтроки()

Это самый очевидный и простой в реализации метод, который подойдет для небольших таблиц (до нескольких тысяч строк). Суть заключается в том, что мы не пытаемся найти все сразу, а ищем каждое значение из фильтра по отдельности.

Проанализируем алгоритм:

  1. Создаем массив для хранения найденных строк.
  2. Обходим СписокЗначений, который является фильтром.
  3. Для каждого элемента вызываем НайтиСтроки(), передавая текущее значение.
  4. Собираем результат, исключая дубликаты (так как одна строка ТЗ может содержать несколько значений из фильтра).

Посмотрим на пример реализации:


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

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

Способ 2. Полный перебор с ручной проверкой вхождения

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


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

Способ 3. Индексация через объект Соответствие (для высокой скорости)

Если ваша таблица содержит десятки тысяч строк, двойной цикл (по таблице и по вложенным спискам) будет работать медленно. Проанализируем ситуацию: нам нужно быстро находить строки по конкретным значениям внутри вложенных списков. Для этого можно заранее построить "индекс".

Рассмотрим использование объекта Соответствие для индексации:

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

Посмотрим на пример кода:


ИндексПредставителей = Новый Соответствие;
Для Каждого СтрокаТЗ Из ТЗ_Данные Цикл
    Для Каждого Элемент Из СтрокаТЗ.ТорговыйПредставитель Цикл
        МассивСтрок = ИндексПредставителей[Элемент.Значение];
        Если МассивСтрок = Неопределено Тогда
            МассивСтрок = Новый Массив;
            ИндексПредставителей.Вставить(Элемент.Значение, МассивСтрок);
        КонецЕсли;
        МассивСтрок.Добавить(СтрокаТЗ);
    КонецЦикла;
КонецЦикла;

// Теперь получение строк по фильтру происходит мгновенно
Для Каждого ЭлементФильтра Из СписокФильтра Цикл
    МассивНаходок = ИндексПредставителей[ЭлементФильтра.Значение];
    Если МассивНаходок <> Неопределено Тогда
        // Обработка найденных строк...
    КонецЕсли;
КонецЦикла;

Способ 4. Нормализация данных (Рекомендуемый подход)

Как отмечалось в обсуждении на форуме, хранение списка в ячейке таблицы — это "странная архитектура", нарушающая принципы нормализации. Выясним, как сделать "правильно". Вместо одной таблицы с колонкой-списком, используем две связанные таблицы или одну плоскую таблицу, где данные развернуты.

Пример плоской таблицы: Если в строке 1 было 3 представителя, в "правильной" таблице будет 3 строки с одинаковыми данными, но разными представителями. К такой таблице можно легко применить обычный запрос с условием В (&Список) и последующей группировкой.

Если вам все же нужно сохранить исходную структуру, создайте служебную таблицу связей:

Теперь задача отбора сводится к простому запросу к этой служебной таблице, который вернет вам список НомерСтрокиОригинала, удовлетворяющих условию.

Резюме

Мы рассмотрели несколько подходов к решению задачи. Выбор конкретного метода зависит от ваших условий:

← На главную