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

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

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

Миф о «несвязанности» результатов поиска: как на самом деле работает НайтиСтроки()

Начнем с разбора классического сомнения, которое часто возникает у начинающих программистов и прозвучало в обсуждении на форуме: «Правда ли, что полученный результат работы функции НайтиСтроки не имеет никакого отношения к исходной таблице?»

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

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


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

// Шаг 2. Ищем строки в табличной части "ДополнительныеОстатки" объекта Номенклатура
НайденныеСтроки = Объект.ДополнительныеОстатки.НайтиСтроки(ПараметрыОтбора);

// Шаг 3. Изменяем реквизиты в цикле
Для Каждого СтрокаТЧ Из НайденныеСтроки Цикл
    СтрокаТЧ.Остаток = 0; // Изменения происходят прямо в ТЧ "ДополнительныеОстатки"!
КонецЦикла;

Таким образом, массив хранит ссылки на строки табличной части. При изменении свойства элемента в массиве значение в самой таблице также гарантированно меняется. Но почему же тогда у автора вопроса «ничего не менялось» после обхода в цикле? Выясним причины этой проблемы подробнее.

Почему изменения в табличной части «исчезают»?

Если код отрабатывает без ошибок, значения реквизитов строк в оперативной памяти меняются, но в базе данных всё остается по-прежнему, наиболее вероятны три причины:

  1. Отсутствие записи самого объекта. Изменение строк табличной части в коде модифицирует только объект, находящийся в оперативной памяти компьютера (в так называемом контексте сервера или клиента). Чтобы зафиксировать эти изменения в базе данных (БД), необходимо явно вызвать метод Записать() для редактируемого прикладного объекта (справочника, документа). Например: ОбъектНоменклатура.Записать();.
  2. Контекст выполнения (Клиент / Сервер). При работе в управляемом приложении попытка напрямую изменить строки на клиенте без контекстного вызова сервера или без последующего сохранения формы может привести к потере данных при обновлении интерфейса. В обычных формах (например, в УПП 1.3) важно следить за тем, изменяется ли реквизит формы напрямую или данные считываются заново из БД, затирая ваши программные правки.
  3. Редактирование копии вместо самого объекта. Если вы получили объект через ссылку (например, СсылкаНаНоменклатуру.ПолучитьОбъект()), внесли изменения в его табличную часть, но забыли вызвать метод Записать() для этого конкретного полученного объекта, изменения пропадут сразу после завершения работы процедуры.

Главная ловушка при удалении строк в цикле: эффект сдвига индексов

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

Рассмотрим пример кода, который категорически нельзя использовать:


// НЕПРАВИЛЬНЫЙ КОД! Приводит к пропуску строк и ошибкам выхода за границы коллекции
Для Индекс = 0 По Объект.Товары.Количество() - 1 Цикл
    Строка = Объект.Товары[Индекс];
    Если Строка.Количество = 0 Тогда
        Объект.Товары.Удалить(Строка); // Или Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Выясним причину, почему этот код работает некорректно. Когда система удаляет строку из таблицы, все последующие за ней строки автоматически сдвигаются на одну позицию вверх (их индексы уменьшаются на единицу). Однако счетчик цикла Индекс послушно увеличивается на единицу на следующей итерации. В результате строка, которая шла сразу же за удаленной, перемещается на место удаленной и получает ее прежний индекс. Цикл благополучно перешагивает через нее, оставляя без проверки и удаления.

Три правильных способа удаления строк по отбору

Для реализации безопасного и быстрого удаления строк в 1С:Предприятии используются проверенные паттерны программирования. Разберем их по шагам.

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

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


ПараметрыОтбора = Новый Структура("Склад", НужныйСклад);
НайденныеСтроки = Объект.НоменклатураТаблица.НайтиСтроки(ПараметрыОтбора);

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

Преимущества: Простой, лаконичный код, легкий для чтения и отладки.

Ограничения: Метод НайтиСтроки работает исключительно по условию точного равенства. Если вам необходимо удалить строки по сложному условию (например, "Остаток < 0" или "Дата < ВыбраннаяДата"), данный способ применить не удастся.

Способ 2. Обход таблицы с конца (Обратный цикл)

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


Индекс = Объект.НоменклатураТаблица.Количество() - 1;

Пока Индекс >= 0 Цикл
    СтрокаТЧ = Объект.НоменклатураТаблица[Индекс];
    
    // Произвольное сложное условие
    Если СтрокаТЧ.Остаток <= 0 Или СтрокаТЧ.Пользователь.Пустая() Тогда
        Объект.НоменклатураТаблица.Удалить(Индекс);
    КонецЕсли;
    
    Индекс = Индекс - 1;
КонецЦикла;

Преимущества: Полная свобода в написании логики условий отбора любой сложности.

Способ 3. Метод «Выгрузить — Загрузить» (Оптимален для больших таблиц)

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

Разберем этот алгоритм по шагам:

  1. Выгружаем строки, которые соответствуют условию «оставить», во временную таблицу значений с помощью встроенного механизма фильтрации метода Выгрузить.
  2. Очищаем исходную табличную часть.
  3. Загружаем сохраненные строки обратно одной быстрой операцией.

// Шаг 1. Формируем структуру отбора для строк, которые ДОЛЖНЫ ОСТАТЬСЯ
// Например, оставляем только строки с заполненным пользователем
ПараметрыОтбора = Новый Структура("Актуально", Истина);
ТЗ_Остается = Объект.НоменклатураТаблица.Выгрузить(ПараметрыОтбора);

// Шаг 2. Загружаем отфильтрованные данные обратно в объект
Объект.НоменклатураТаблица.Загрузить(ТЗ_Остается);

Преимущества: Максимальная скорость работы на больших объемах данных за счет минимизации обращений к интерфейсным механизмам и оптимизации пакетных операций на уровне платформы.

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

Для изменения строк табличной части смело используйте метод НайтиСтроки — он предоставляет прямой доступ к элементам. Не забывайте вызывать метод Записать() для объекта после внесения любых правок в коде.

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

← На главную