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