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