В процессе разработки в системе «1С:Предприятие» часто возникает задача удаления определенных строк из табличной части документа. Ситуация осложняется, когда критерием удаления является именно номер строки. Главная проблема заключается в том, что после удаления первой же строки индексы всех последующих строк уменьшаются на единицу, и классический перебор «сверху вниз» приводит к пропуску нужных строк или удалению неверных данных. Рассмотрим несколько эффективных способов решения этой задачи, от простых циклов до высокопроизводительных методов работы с памятью.
Разберем ситуацию: у нас есть документ, содержащий 4000 строк в табличной части Материалы. Нам нужно удалить 1000 строк, номера которых хранятся в отдельной таблице или массиве. Если мы начнем удалять строки по порядку (1, 5, 10...), то после удаления строки №1 строка, которая была №2, станет №1. В итоге наш алгоритм, обратившись к следующему индексу, пропустит данные или обратится к некорректной позиции. Проанализируем способы, позволяющие этого избежать.
Это классический и самый надежный метод при работе с индексами коллекций. Суть заключается в том, что мы начинаем перебор табличной части с конца (с самого большого номера к меньшему). В этом случае удаление нижней строки никак не влияет на номера строк, расположенных выше.
Рассмотрим пример кода, где СтрокиВДокументах — это таблица с номерами строк для удаления, а ТН — ссылка на документ:
ДокОбъект = ТН.ПолучитьОбъект();
КоллекцияСтрок = ДокОбъект.Материалы;
// Сортируем таблицу с номерами строк по убыванию, чтобы идти снизу вверх
СтрокиВДокументах.Сортировать("СтрокаТН Убыв");
Для Каждого СтрИзСписка Из СтрокиВДокументах Цикл
НомерДляПоиска = Число(СтрИзСписка.СтрокаТН);
// Ищем строку по ее номеру. Важно: НомерСтроки — это штатный реквизит ТЧ
СтрокаТЧ = КоллекцияСтрок.Найти(НомерДляПоиска, "НомерСтроки");
Если СтрокаТЧ <> Неопределено Тогда
КоллекцияСтрок.Удалить(СтрокаТЧ);
КонецЕсли;
КонецЦикла;
ДокОбъект.Записать();
Важный нюанс: при использовании метода Удалить(СтрокаОбъект) платформа сама находит нужный индекс. Если же вы используете удаление по индексу Удалить(Индекс), помните, что в 1С НомерСтроки начинается с 1, а индексы в коллекции — с 0.
Метод НайтиСтроки() возвращает массив прямых ссылок на объекты строк табличной части. Это позволяет сначала собрать все нужные строки в одну коллекцию (массив), а затем разом их удалить. Поскольку в массиве хранятся ссылки на объекты строк, а не их номера, смещение индексов внутри табличной части при удалении не мешает платформе корректно определять нужные объекты.
Выясним, как это реализовать на практике:
ОбъектДок = ТН.ПолучитьОбъект();
ТабличнаяЧасть = ОбъектДок.Материалы;
МассивСтрокДляУдаления = Новый Массив;
// 1. Сначала собираем ссылки на строки, которые нужно удалить
Для каждого ЗаписьИзСписка из СтрокиВДокументах Цикл
Если ЗначениеЗаполнено(ЗаписьИзСписка.СтрокаТН) Тогда
СтруктураПоиска = Новый Структура("НомерСтроки", Число(ЗаписьИзСписка.СтрокаТН));
НайденныеСтроки = ТабличнаяЧасть.НайтиСтроки(СтруктураПоиска);
Для каждого Стр Из НайденныеСтроки Цикл
МассивСтрокДляУдаления.Добавить(Стр);
КонецЦикла;
КонецЕсли;
КонецЦикла;
// 2. Удаляем собранные строки
Для каждого СтрДляУдаления Из МассивСтрокДляУдаления Цикл
ТабличнаяЧасть.Удалить(СтрДляУдаления);
КонецЦикла;
ОбъектДок.Записать();
Если в документе очень много строк (десятки тысяч) и нужно удалить значительную их часть, поштучное удаление методом Удалить() может работать медленно. Это связано с тем, что после каждого удаления 1С пересчитывает итоги документа и обновляет индексы коллекции. В таких случаях эффективнее использовать метод «полной замены».
Проанализируем шаги этого алгоритма:
ТаблицаЗначений.Очистить().Загрузить().Посмотрим на пример реализации:
ДокОбъект = ТН.ПолучитьОбъект();
ТЗ_Материалы = ДокОбъект.Materials.Выгрузить();
// Добавим колонку для пометки на удаление (для удобства фильтрации)
ТЗ_Материалы.Колонки.Добавить("НужноУдалить", Новый ОписаниеТипов("Булево"));
// Заполняем пометки на основе вашего списка номеров строк
Для каждого Стр Из СтрокиВДокументах Цикл
Номер = Число(Стр.СтрокаТН);
// Номер строки в ТЗ соответствует (НомерСтроки - 1)
Если Номер > 0 И Номер <= ТЗ_Материалы.Количество() Тогда
ТЗ_Материалы[Номер - 1].НужноУдалить = Истина;
КонецЕсли;
КонецЦикла;
// Отбираем только те строки, которые НЕ помечены на удаление
ПараметрыОтбора = Новый Структура("НужноУдалить", Ложь);
ТЗ_Остаток = ТЗ_Материалы.Скопировать(ПараметрыОтбора);
// Удаляем служебную колонку перед загрузкой назад
ТЗ_Остаток.Колонки.Удалить("НужноУдалить");
// Обновляем документ
ДокОбъект.Материалы.Загрузить(ТЗ_Остаток);
ДокОбъект.Записать();
Для обеспечения стабильной работы программы при массовых операциях со строками рекомендуем придерживаться следующих правил:
ПолучитьОбъект()), перебору строк и записи должны выполняться в серверном контексте. Передача тяжелых объектов между клиентом и сервером существенно замедляет работу.НачатьТранзакцию() ... ЗафиксироватьТранзакцию()). Это обеспечит целостность данных в случае ошибки.Число() и предварительную очистку строки через СтрЗаменить(Текст, Символы.НПП, "").Рассмотренные подходы позволяют гибко решать задачу удаления данных. Для небольших документов идеально подходит обратный цикл, а для высоконагруженных систем лучшим выбором будет выгрузка в таблицу значений с последующей загрузкой отфильтрованного результата.