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

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

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

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


мОсновныеСклады = Новый СписокЗначений;
мОсновныеСклады.Добавить(Справочники.Склады.НайтиПоНаименованию("Основной склад №1"));
мОсновныеСклады.Добавить(Справочники.Склады.НайтиПоНаименованию("Основной склад №2"));

// Попытка отбора через НайтиСтроки
Отбор = Новый Структура;
Отбор.Вставить("Склад", мОсновныеСклады);
рОстаткиЦС = Остатки.НайтиСтроки(Отбор);

В результате метод НайтиСтроки() возвращает пустой массив. Выясним причину такого поведения платформы.

Почему метод НайтиСтроки не работает со списками значений

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

Кроме того, в НайтиСтроки() невозможно применить логические операторы НЕ, В, СОДЕРЖИТ или сравнения на больше/меньше. Для решения задачи отбора по списку или массиву необходимо использовать альтернативные подходы.

Способ 1. Использование временной таблицы в запросе

Классический способ фильтрации готовой таблицы значений — передача ее в объект Запрос в качестве параметра и обработка средствами языка запросов 1С.

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

Разберем по шагам, как построить фильтрацию:

  1. Создаем типизированную таблицу значений и заполняем ее данными.
  2. Помещаем таблицу во временную таблицу в запросе с помощью конструкции ПОМЕСТИТЬ.
  3. Применяем операторы В (&Список) или НЕ В (&Список) в секции ГДЕ.
  4. Выгружаем результат запроса обратно в новую таблицу значений.

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


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

// Заполняем таблицу данными...

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Остатки.Склад КАК Склад,
    |    Остатки.Остаток КАК Остаток
    |ПОМЕСТИТЬ ВТОстатки
    |ИЗ
    |    &Остатки КАК Остатки
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    Остатки.Склад КАК Склад,
    |    Остатки.Остаток КАК Остаток
    |ИЗ
    |    ВТОстатки КАК Остатки
    |ГДЕ
    |    Остатки.Склад В (&ОсновныеСклады)";

Запрос.УстановитьПараметр("Остатки", Остатки);
Запрос.УстановитьПараметр("ОсновныеСклады", мОсновныеСклады);

рОстаткиЦС = Запрос.Выполнить().Выгрузить();

Если нам требуется получить сразу два набора данных — строки, входящие в список, и строки, не входящие в него, — крайне не рекомендуется вызывать Запрос.Выполнить() дважды с заменой текста. Повторное выполнение приведет к двойной передаче таблицы в СУБД. Оптимально использовать пакетный запрос с вызовом ВыполнитьПакет():


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Остатки.Склад КАК Склад,
    |    Остатки.Остаток КАК Остаток
    |ПОМЕСТИТЬ ВТОстатки
    |ИЗ
    |    &Остатки КАК Остатки
    |;
    |
    |// Первый результат: входящие в список
    |ВЫБРАТЬ
    |    Остатки.Склад КАК Склад,
    |    Остатки.Остаток КАК Остаток
    |ИЗ
    |    ВТОстатки КАК Остатки
    |ГДЕ
    |    Остатки.Склад В (&ОсновныеСклады);
    |
    |// Второй результат: не входящие в список
    |ВЫБРАТЬ
    |    Остатки.Склад КАК Склад,
    |    Остатки.Остаток КАК Остаток
    |ИЗ
    |    ВТОстатки КАК Остатки
    |ГДЕ
    |    НЕ Остатки.Склад В (&ОсновныеСклады)";

Запрос.УстановитьПараметр("Остатки", Остатки);
Запрос.УстановитьПараметр("ОсновныеСклады", мОсновныеСклады);

МассивРезультатов = Запрос.ВыполнитьПакет();
рОстаткиЦС = МассивРезультатов[1].Выгрузить();
рОстаткиУС = МассивРезультатов[2].Выгрузить();

Способ 2. Использование объекта ПостроительЗапроса

Объект ПостроительЗапроса позволяет накладывать отборы на таблицы значений напрямую, без необходимости ручного написания SQL-подобного текста с временными таблицами. При этом он поддерживает вид сравнения ВСписке.

Рассмотрим пример настройки отбора через построитель:


ПостроительЗапроса = Новый ПостроительЗапроса;
// В качестве источника данных передаем исходную таблицу значений
ПостроительЗапроса.ИсточникДанных = Новый ОписаниеИсточникаДанных(Остатки);

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

ПостроительЗапроса.Выполнить();
рОстаткиЦС = ПостроительЗапроса.Результат.Выгрузить();

Этот способ компактен, однако следует помнить, что внутри платформы ПостроительЗапроса при обработке источника данных также генерирует временные структуры.

Способ 3. Быстрая фильтрация в памяти через Соответствие

Если операция отбора выполняется часто (например, в обработчиках интерфейса ПриАктивизацииСтроки), передача таблицы в СУБД через запрос создает избыточную нагрузку на сеть и базу данных. В этом случае значительно быстрее выполнить фильтрацию прямо в оперативной памяти с помощью объекта Соответствие.

Поиск по ключу в объекте Соответствие работает с алгоритмической сложностью O(1), что обеспечивает мгновенную проверку условий даже на больших объемах данных.

Разберем по шагам алгоритм разделения строк:

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

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


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

// Создаем пустые таблицы с исходной структурой колонок
рОстаткиЦС = Остатки.СкопироватьКолонки();
рОстаткиУС = Остатки.СкопироватьКолонки();

// Разделяем строки за один проход
Для Каждого СтрОстаток Из Остатки Цикл
    Если СкладыСоответствие[СтрОстаток.Склад] <> Неопределено Тогда
        ЗаполнитьЗначенияСвойств(рОстаткиЦС.Добавить(), СтрОстаток);
    Иначе
        ЗаполнитьЗначенияСвойств(рОстаткиУС.Добавить(), СтрОстаток);
    КонецЕсли;
КонецЦикла;

Способ 4. Индексация таблицы значений и метод Скопировать

Если список отбора содержит небольшое количество значений (например, 2–3 склада), а исходная таблица насчитывает десятки тысяч строк, эффективным решением будет индексация исходной таблицы:


// Добавляем индекс для ускорения НайтиСтроки
Остатки.Индексы.Добавить("Склад");

НайденныеСтроки = Новый Массив;
Отбор = Новый Структура("Склад");

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

// Создаем результирующую таблицу из массива строк
рОстаткиЦС = Остатки.Скопировать(НайденныеСтроки);

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

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

← На главную