Как отобрать строки табличной части документа по списку значений с возможностью их изменения

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

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

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

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

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


Отбор = Новый Структура;
Отбор.Вставить("Номенклатура", МассивТоваров);
Строки = Заказ.Товары.НайтиСтроки(Отбор);

Этот код вернет пустой массив. Выясним причину такого поведения: метод НайтиСтроки() работает исключительно по точному равенству значений. Платформа ищет в табличной части строку, реквизит Номенклатура которой буквально равен переданному объекту Массив (а не одному из элементов этого массива). Операторы сравнения вроде «В списке» или «Содержит» данным методом не поддерживаются.

Кроме того, в платформе 1С не существует отдельного объекта-«среза» с типом ТабличнаяЧасть, который мог бы содержать только часть строк исходного документа. Однако объект СтрокаТабличнойЧасти передается по ссылке. Это означает, что если поместить ссылки на нужные строки в обычный Массив, то при изменении любых свойств строки внутри массива (например, Стр.Количество = 10) данные моментально изменятся и в табличной части документа в оперативной памяти.

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

Способ 1. Обход табличной части с использованием объекта Соответствие (рекомендуемый)

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

Разберем по шагам реализацию данного алгоритма:

  1. Создаем объект Соответствие и заполняем его искомыми значениями номенклатуры.
  2. Создаем пустой массив для сбора отобранных строк.
  3. В цикле обходим табличную часть документа и проверяем каждый элемент.
  4. Если товар найден в соответствии, добавляем саму строку в массив.

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

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

// Теперь ОтобранныеСтроки можно передать в процедуру обработки:
// Изменение реквизитов строк внутри массива изменит их и в Заказ.Товары
Для Каждого Стр Из ОтобранныеСтроки Цикл
    Стр.Цена = Стр.Цена * 1.1; // Данные изменятся прямо в Заказ.Товары
КонецЦикла;

Этот способ оптимален тем, что выполняется ровно один проход по табличной части документа, не создаются дубли строк и не расходуются ресурсы СУБД.

Способ 2. Использование НайтиСтроки в цикле по списку

Если список условий заведомо короткий, можно использовать вызов НайтиСтроки() для каждого конкретного товара. Посмотрим на пример реализации:


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

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

Обратите внимание: если табличная часть содержит много строк, а список отбора велик, такой код будет работать медленнее первого способа из-за многократного полного сканирования таблицы методом НайтиСтроки().

Способ 3. Отбор номеров строк через запрос к объекту

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

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


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

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

Выборка = Запрос.Выполнить().Выбрать();
ОтобранныеСтроки = Новый Массив;

Пока Выборка.Следующий() Цикл
    // В 1С индексация коллекции табличной части начинается с нуля,
    // а реквизит НомерСтроки нумеруется с единицы
    ИндексСтроки = Выборка.НомерСтроки - 1;
    ОтобранныеСтроки.Добавить(Заказ.Товары[ИндексСтроки]);
КонецЦикла;

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

Ограничения при передаче массива строк в процедуры

Хотя массив элементов СтрокаТабличнойЧасти позволяет напрямую модифицировать данные документа, следует учитывать поведение целевой процедуры:

  1. Проверка типа коллекции: если внутри вызываемой процедуры есть жесткая проверка вида ТипЗнч(Коллекция) = Тип("ТабличнаяЧасть"), передача массива вызовет ошибку. В таком случае процедуру потребуется адаптировать под универсальный обход коллекций.
  2. Методы табличной части: у массива отсутствуют специализированные методы табличных частей, такие как .Итог(), .Добавить() или .Очистить(). Если процедура вызывает эти методы, передать массив вместо табличной части не удастся.
  3. Выгрузка в ТаблицуЗначений: если процедуре требуется именно таблица, можно выгрузить отобранные строки методом самой табличной части: ТЗ = Заказ.Товары.Выгрузить(ОтобранныеСтроки);. Однако изменения в полученной таблице значений уже не будут автоматически транслироваться в документ и потребуют обратной загрузки через Заказ.Товары.Загрузить().
← На главную