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

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

Процесс программного изменения документов в 1С, особенно когда речь идет о массовом удалении строк из табличных частей, требует четкого понимания работы с объектной моделью платформы. Часто разработчики сталкиваются с ошибкой "Колонка не принадлежит коллекции" или замечают, что код отрабатывает без ошибок, но данные в базе не меняются. В этой статье мы подробно разберем причины таких проблем и напишем надежный алгоритм для очистки табличных частей.

Анализ исходной проблемы и поиск ошибки

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


для каждого стр из выборка[тч.Значение] цикл 
    если СписокТовары.найти(стр.товар)<>Неопределено тогда 
        док[тч.Значение].удалить(стр); 
    КонецЕсли; 
КонецЦикла;

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

Правильный подход к удалению строк в цикле

Чтобы исправить ситуацию, нам необходимо обращаться к строкам того же объекта, который мы редактируем. Выясним, как это сделать правильно. Рассмотрим исправленный вариант цикла:


// Вместо обращения к выборке, обращаемся к объекту
для каждого стр из док[тч.Значение] цикл 
    // Логика поиска и пометки строк для удаления
КонецЦикла;

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

Способ 1: Использование вспомогательного массива (рекомендуется)

Сначала мы находим все подходящие строки и складываем их в массив, а затем отдельным циклом удаляем их из коллекции документа.


СписокУдалСтр = новый Массив;
Для каждого Стр Из Док[ИмяТЧ] Цикл
    Если Стр.Товар = ИскомыйТовар Тогда
        СписокУдалСтр.Добавить(Стр);
    КонецЕсли;
КонецЦикла;

Для каждого СтрУдаления Из СписокУдалСтр Цикл
    Док[ИмяТЧ].Удалить(СтрУдаления);
КонецЦикла;

Способ 2: Обход с конца коллекции

Если мы используем цикл по индексу, то, двигаясь от последней строки к первой, мы избегаем проблем со смещением индексов.


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

Оптимизация поиска документов через запросы

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

Вместо перебора метаданных, составим запрос к табличным частям:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ РАЗЛИЧНЫЕ
|   ТЧ.Ссылка КАК Ссылка
|ИЗ
|   Документ.РеализацияТоваровУслуг.Товары КАК ТЧ
|ГДЕ
|   ТЧ.Номенклатура В ИЕРАРХИИ(&Товар)";

Запрос.УстановитьПараметр("Товар", НужныйТовар);
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    ДокОбъект = Выборка.Ссылка.ПолучитьОбъект();
    // Далее логика удаления строк...
КонецЦикла;

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

Работа с транзакциями и блокировками

Рассмотрим важный аспект надежности. Использование одной огромной транзакции на весь цикл обработки (как было в исходном коде сообщения) — это опасная практика. Если произойдет ошибка на 500-м документе, изменения во всех предыдущих 499 документах будут отменены. Кроме того, база данных будет заблокирована для других пользователей на все время работы обработки.

Рекомендуемая стратегия:

  1. Открывайте транзакцию только для записи одного конкретного документа или небольшой группы (например, по 50 штук).
  2. Используйте конструкцию Попытка...Исключение при записи каждого объекта.
  3. Если документ был проведен, не забудьте перепровести его, иначе движения по регистрам (остатки, продажи) останутся старыми, несмотря на удаление строки в табличной части.

Если НадоЗаписать Тогда
    Попытка
        Режим = ?(ДокОбъект.Проведен, РежимЗаписиДокумента.Проведение, РежимЗаписиДокумента.Запись);
        ДокОбъект.Записать(Режим);
    Исключение
        Сообщить("Не удалось записать документ " + ДокОбъект + ": " + ОписаниеОшибки());
    КонецПопытки;
КонецЕсли;

Использование метода НайтиСтроки

Для еще большего упрощения кода мы можем использовать метод НайтиСтроки(). Этот метод встроен в объект табличной части и позволяет мгновенно получить массив строк по заданному отбору.


Отбор = Новый Структура("Товар", ИскомыйТовар);
СтрокиДляУдаления = ДокОбъект.Товары.НайтиСтроки(Отбор);

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

Это делает код более читаемым и производительным, так как платформа оптимизирует поиск строк внутри коллекции.

Технический нюанс: ОбменДанными.Загрузка

Если ваша задача — выполнить техническую корректировку данных и вы уверены в корректности своих действий, можно ускорить запись документов, установив свойство ОбменДанными.Загрузка = Истина. Это отключит проверки в модуле объекта (например, проверку наличия остатков или заполненности реквизитов), что значительно повысит скорость обработки.


ДокОбъект.ОбменДанными.Загрузка = Истина;
ДокОбъект.Записать();

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

Подводя итог, мы проанализировали основные ошибки при удалении строк из ТЧ документов и выработали эффективный алгоритм. Помните о разнице между выборкой и объектом, используйте запросы для предварительного отбора данных и всегда обрабатывайте транзакции локально для каждого документа.

← На главную