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

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

В процессе разработки часто возникает задача очистки табличных частей документов по определенному условию — например, когда характеристики номенклатуры помечены на удаление. Начинающие разработчики часто пытаются удалять строки непосредственно в цикле обхода результата запроса, что приводит к логическим ошибкам или падению системы. Разберем, как правильно реализовать этот механизм, обеспечив стабильность и производительность кода.

Почему нельзя удалять строки напрямую из результата запроса?

Запрос в 1С работает с данными, которые уже записаны в базе данных (в таблицах СУБД). Когда мы получаем результат запроса, мы работаем с «копией» состояния данных на момент выполнения. Попытка удалить строку, используя индекс из запроса, часто наталкивается на проблему: при удалении первой же строки в объекте ДокументОбъект индексы всех последующих строк в коллекции смещаются. Если в документе было 10 строк и мы удалили 5-ю, то бывшая 6-я строка станет 5-й. В итоге цикл по старым индексам либо пропустит нужные строки, либо вызовет ошибку выхода за границы массива.

Кроме того, в самом запросе невозможно получить «прямую ссылку» на строку табличной части, так как строки не являются ссылочными объектами. Мы можем получить только НомерСтроки или ссылку на сам документ-владелец.

Алгоритм безопасного удаления строк через вспомогательный массив

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

Рассмотрим пример кода, который обходит документы и удаляет строки с помеченными на удаление характеристиками:


// 1. Выполняем запрос для получения документов и номеров строк
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПоступлениеТоваровУслугТовары.Ссылка КАК Ссылка,
    |	ПоступлениеТоваровУслугТовары.НомерСтроки КАК НомерСтроки
    |ИЗ
    |	Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
    |ГДЕ
    |	ПоступлениеТоваровУслугТовары.Характеристика.ПометкаУдаления = ИСТИНА
    |ИТОГИ ПО
    |	Ссылка";

РезультатЗапроса = Запрос.Выполнить();
ВыборкаДокумент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

// 2. Обходим документы
Пока ВыборкаДокумент.Следующий() Цикл
    
    ДокОбъект = ВыборкаДокумент.Ссылка.ПолучитьОбъект();
    МассивСтрокДляУдаления = Новый Массив;
    
    // Получаем выборку строк для конкретного документа
    ВыборкаСтрок = ВыборкаДокумент.Выбрать();
    Пока ВыборкаСтрок.Следующий() Цикл
        // Индекс строки в коллекции равен НомерСтроки - 1
        ТекущаяСтрока = ДокОбъект.Товары.Получить(ВыборкаСтрок.НомерСтроки - 1);
        МассивСтрокДляУдаления.Добавить(ТекущаяСтрока);
    КонецЦикла;
    
    // 3. Удаляем строки из коллекции документа по объектам строк
    Для Каждого Стр Из МассивСтрокДляУдаления Цикл
        ДокОбъект.Товары.Удалить(Стр);
    КонецЦикла;
    
    // Записываем изменения
    Если МассивСтрокДляУдаления.Количество() > 0 Тогда
        ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
    КонецЕсли;
    
КонецЦикла;

Оптимизация работы с объектами и выборками

Разберем важные моменты оптимизации, которые помогут коду работать быстрее:

  1. Группировка по ссылкам: Использование ИТОГИ ПО Ссылка в запросе критически важно. Это позволяет нам вызвать ПолучитьОбъект() один раз для каждого документа, даже если в нем нужно удалить 100 строк. Работа с объектом в памяти — ресурсоемкая операция, и ее минимизация ускоряет обработку в разы.
  2. Метод Удалить(): Обратите внимание, что метод Удалить() у табличной части может принимать в качестве параметра как индекс (число), так и саму строку (объект СтрокаТабличнойЧасти). Использование объекта строки в цикле по массиву — самый безопасный путь.
  3. Использование НомерСтроки: В запросе мы получаем НомерСтроки. Важно помнить, что нумерация строк в ТЧ начинается с 1, а индексы в программном коде — с 0. Поэтому для получения строки используется конструкция Получить(ВыборкаСтрок.НомерСтроки - 1).

Альтернативные методы удаления

Проанализируем ситуацию, когда строк в документе очень много (например, десятки тысяч). В этом случае стандартное удаление в цикле может работать медленно из-за постоянного пересчета индексов коллекции 1С.

Метод выгрузки в Таблицу Значений:
Можно выгрузить табличную часть в таблицу значений методом Выгрузить(), удалить лишние строки там (это работает быстрее), полностью очистить табличную часть документа методом Очистить() и загрузить данные обратно методом Загрузить().

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


// Пример реверсивного цикла
КоллекцияТоваров = ДокОбъект.Товары;
Для Счетчик = -(КоллекцияТоваров.Количество() - 1) По 0 Цикл
    ТекущийИндекс = -Счетчик;
    СтрокаТЧ = КоллекцияТоваров[ТекущийИндекс];
    Если СтрокаТЧ.Характеристика.ПометкаУдаления Тогда
        КоллекцияТоваров.Удалить(ТекущийИндекс);
    КонецЕсли;
КонецЦикла;

Важные рекомендации по учету

При программном изменении документов, особенно таких как ПоступлениеТоваровУслуг, необходимо учитывать статус документа. Если документ уже проведен, удаление строк может привести к рассогласованию остатков на складах и в регистрах. В таких случаях рекомендуем использовать ДокОбъект.Записать(РежимЗаписиДокумента.Проведение), чтобы система автоматически пересчитала движения по регистрам после изменения состава табличной части.

Также перед выполнением массовых обработок всегда проверяйте права доступа текущего пользователя и наличие блокировок на редактируемые периоды (даты запрета редактирования).

← На главную