При работе с табличными частями документов или справочников в среде 1С:Предприятие разработчики часто сталкиваются с необходимостью быстро найти определенные строки, изменить их значения или полностью удалить. На первый взгляд задача кажется тривиальной, однако на практике возникают скрытые нюансы: изменения не сохраняются в базе данных, строки при удалении «пропускаются», а производительность системы падает. В этой статье мы подробно разберем, как правильно использовать метод НайтиСтроки, выясним причины типичных ошибок и рассмотрим лучшие практики работы с коллекциями строк.
Начнем с разбора классического сомнения, которое часто возникает у начинающих программистов и прозвучало в обсуждении на форуме: «Правда ли, что полученный результат работы функции НайтиСтроки не имеет никакого отношения к исходной таблице?»
Это не так. Метод НайтиСтроки возвращает коллекцию типа Массив, элементами которой являются прямые ссылки на строки исходной табличной части (или таблицы значений). Это означает, что оперативная память не дублирует данные строк, а хранит лишь указатели на них.
Проанализируем ситуацию на примере. Если мы получаем строку через поиск и меняем ее реквизит, это изменение мгновенно отражается в исходной табличной части:
// Шаг 1. Настраиваем структуру отбора
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Склад", Справочники.Склады.НайтиПоКоду("00001"));
// Шаг 2. Ищем строки в табличной части "ДополнительныеОстатки" объекта Номенклатура
НайденныеСтроки = Объект.ДополнительныеОстатки.НайтиСтроки(ПараметрыОтбора);
// Шаг 3. Изменяем реквизиты в цикле
Для Каждого СтрокаТЧ Из НайденныеСтроки Цикл
СтрокаТЧ.Остаток = 0; // Изменения происходят прямо в ТЧ "ДополнительныеОстатки"!
КонецЦикла;
Таким образом, массив хранит ссылки на строки табличной части. При изменении свойства элемента в массиве значение в самой таблице также гарантированно меняется. Но почему же тогда у автора вопроса «ничего не менялось» после обхода в цикле? Выясним причины этой проблемы подробнее.
Если код отрабатывает без ошибок, значения реквизитов строк в оперативной памяти меняются, но в базе данных всё остается по-прежнему, наиболее вероятны три причины:
Записать() для редактируемого прикладного объекта (справочника, документа). Например: ОбъектНоменклатура.Записать();.СсылкаНаНоменклатуру.ПолучитьОбъект()), внесли изменения в его табличную часть, но забыли вызвать метод Записать() для этого конкретного полученного объекта, изменения пропадут сразу после завершения работы процедуры.Перейдем ко второму вопросу автора темы: как правильно удалить найденные строки? При попытке удалять строки прямо во время обхода коллекции в цикле разработчики регулярно совершают одну и ту же критическую ошибку.
Рассмотрим пример кода, который категорически нельзя использовать:
// НЕПРАВИЛЬНЫЙ КОД! Приводит к пропуску строк и ошибкам выхода за границы коллекции
Для Индекс = 0 По Объект.Товары.Количество() - 1 Цикл
Строка = Объект.Товары[Индекс];
Если Строка.Количество = 0 Тогда
Объект.Товары.Удалить(Строка); // Или Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Выясним причину, почему этот код работает некорректно. Когда система удаляет строку из таблицы, все последующие за ней строки автоматически сдвигаются на одну позицию вверх (их индексы уменьшаются на единицу). Однако счетчик цикла Индекс послушно увеличивается на единицу на следующей итерации. В результате строка, которая шла сразу же за удаленной, перемещается на место удаленной и получает ее прежний индекс. Цикл благополучно перешагивает через нее, оставляя без проверки и удаления.
Для реализации безопасного и быстрого удаления строк в 1С:Предприятии используются проверенные паттерны программирования. Разберем их по шагам.
Этот способ основан на решении, предложенном в ветке форума. Поскольку метод НайтиСтроки возвращает статичный массив ссылок, удаление элементов из исходной табличной части по этим ссылкам никак не влияет на структуру самого массива. Индексы в массиве не сдвигаются, что делает обход абсолютно безопасным.
ПараметрыОтбора = Новый Структура("Склад", НужныйСклад);
НайденныеСтроки = Объект.НоменклатураТаблица.НайтиСтроки(ПараметрыОтбора);
// Безопасное удаление по ссылкам из массива
Для Каждого СтрокаДляУдаления Из НайденныеСтроки Цикл
Объект.НоменклатураТаблица.Удалить(СтрокаДляУдаления);
КонецЦикла;
Преимущества: Простой, лаконичный код, легкий для чтения и отладки.
Ограничения: Метод НайтиСтроки работает исключительно по условию точного равенства. Если вам необходимо удалить строки по сложному условию (например, "Остаток < 0" или "Дата < ВыбраннаяДата"), данный способ применить не удастся.
Если условие фильтрации является сложным или составным, лучшим решением будет классический обход коллекции с конца к ее началу. При удалении строк с конца таблицы сдвигаются только те строки, индексы которых цикл уже прошел, поэтому структура оставшейся (еще не проверенной) части таблицы остается неизменной.
Индекс = Объект.НоменклатураТаблица.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТЧ = Объект.НоменклатураТаблица[Индекс];
// Произвольное сложное условие
Если СтрокаТЧ.Остаток <= 0 Или СтрокаТЧ.Пользователь.Пустая() Тогда
Объект.НоменклатураТаблица.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
Преимущества: Полная свобода в написании логики условий отбора любой сложности.
Если ваша табличная часть содержит тысячи строк, последовательное удаление элементов в цикле может вызывать серьезные задержки, так как при каждом удалении платформе приходится перестраивать индексы коллекции в памяти. В целях оптимизации быстродействия мы можем применить подход с промежуточным сохранением только нужных данных.
Разберем этот алгоритм по шагам:
Выгрузить.
// Шаг 1. Формируем структуру отбора для строк, которые ДОЛЖНЫ ОСТАТЬСЯ
// Например, оставляем только строки с заполненным пользователем
ПараметрыОтбора = Новый Структура("Актуально", Истина);
ТЗ_Остается = Объект.НоменклатураТаблица.Выгрузить(ПараметрыОтбора);
// Шаг 2. Загружаем отфильтрованные данные обратно в объект
Объект.НоменклатураТаблица.Загрузить(ТЗ_Остается);
Преимущества: Максимальная скорость работы на больших объемах данных за счет минимизации обращений к интерфейсным механизмам и оптимизации пакетных операций на уровне платформы.
Для изменения строк табличной части смело используйте метод НайтиСтроки — он предоставляет прямой доступ к элементам. Не забывайте вызывать метод Записать() для объекта после внесения любых правок в коде.
При необходимости удаления строк выбирайте метод в зависимости от задачи: для простых условий равенства отлично подходит удаление элементов по массиву из НайтиСтроки, для сложных алгоритмов — обратный цикл с конца таблицы, а для высоконагруженных систем с большими таблицами — пакетный метод Выгрузить/Загрузить.