К статье
Практический материал

Разработка и Конфигурация · 11 мин

Как удалить строку в цикле в 1С: рабочие приёмы и типичные ошибки

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

Удаление строк табличной части документа или таблицы значений прямо в цикле перебора — одна из самых частых причин ошибок у начинающих разработчиков 1С. Если удалять элементы коллекции во время её прямого обхода, индексы сдвигаются, часть строк пропускается, а в худшем случае возникает ошибка обращения по несуществующему индексу. При этом сама задача — удалить строки по условию — встречается постоянно: очистка дублей, удаление нулевых остатков, фильтрация данных перед проведением документа.

Встроенный язык 1С не имеет специального «безопасного» итератора для удаления, поэтому применяются несколько устоявшихся приёмов: обратный обход коллекции с конца, предварительный сбор строк в отдельный массив и последующее удаление, а для таблиц значений — фильтрация через параметры обхода или построение новой таблицы. Каждый способ имеет свои ограничения по производительности и применимости.

Ниже разобраны рабочие варианты кода для управляемых и обычных форм, различия между табличной частью объекта и таблицей значений, а также способы проверить результат и не сломать данные в реальной базе.

Почему нельзя удалять строки в прямом цикле перебора

Коллекции в 1С (табличные части, таблицы значений, массивы строк) индексируются от нуля. Когда вы удаляете строку с индексом 2, все последующие строки смещаются на одну позицию вверх: бывшая строка с индексом 3 становится строкой с индексом 2. Цикл «Для Каждого» или «Для Инд = 0 По …» при этом продолжает идти вперёд и переходит к следующему индексу — то есть фактически перескакивает через строку, которая только что заняла место удалённой.

Типичный симптом такой ошибки: после обработки в табличной части остаются строки, которые должны были быть удалены, причём закономерность кажется случайной. На маленьких таблицах из 2–3 строк ошибка может вообще не проявиться, поэтому код «успешно» проходит тестирование и ломается на реальных объёмах данных.

Дополнительный риск — обращение по индексу за границами коллекции. Если цикл построен как «Для Инд = 0 По Таблица.Количество() - 1», а количество строк уменьшается внутри цикла, на последних итерациях произойдёт обращение к несуществующему элементу и выполнение прервётся с ошибкой.

Способ 1: обратный обход коллекции с конца

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

ТабличнаяЧасть = Объект.Товары;

Индекс = ТабличнаяЧасть.Количество() - 1;

Пока Индекс >= 0 Цикл

Строка = ТабличнаяЧасть[Индекс];

Если Строка.Количество = 0 Тогда

ТабличнаяЧасть.Удалить(Индекс);

КонецЕсли;

Индекс = Индекс - 1;

КонецЦикла;

Обратите внимание на детали: количество строк вычисляется один раз до начала цикла, а индекс уменьшается вручную. Условие «Пока Индекс >= 0» гарантирует обработку нулевой строки. Если случайно написать «Индекс > 0», первая строка таблицы никогда не будет проверена — это частая опечатка.

Метод Удалить() принимает как индекс, так и саму строку. При обратном обходе удобнее передавать индекс — это исключает неоднозначность, если в коллекции есть строки с одинаковым содержимым.

Способ 2: сбор строк в массив и удаление после цикла

Когда условие удаления сложное или код внутри цикла уже перегружен, читабельнее сначала собрать строки-кандидаты в отдельный массив, а затем удалить их одним проходом. Коллекция при этом не меняется во время перебора, поэтому можно использовать привычный цикл «Для Каждого».

СтрокиКУдалению = Новый Массив;

Для Каждого Строка Из Объект.Товары Цикл

Если Строка.Сумма = 0 Или Строка.Номенклатура.Пустая() Тогда

СтрокиКУдалению.Добавить(Строка);

КонецЕсли;

КонецЦикла;

Для Каждого Строка Из СтрокиКУдалению Цикл

Объект.Товары.Удалить(Строка);

КонецЦикла;

Здесь в Удалить() передаётся сама строка, а не индекс, поэтому порядок удаления не важен. Способ особенно удобен, когда нужно не просто удалить строки, а предварительно что-то с ними сделать: записать в протокол, перенести в другую таблицу, показать пользователю список удаляемых позиций для подтверждения.

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

Сравнение вариантов

Обратный обход

Быстрый, один проход, без дополнительной памяти. Подходит для большинства задач, но цикл менее очевиден при чтении кода.

Массив к удалению

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

Новая таблица через запрос или НайтиСтроки

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

Особенности для таблицы значений

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

Комбинация методов НайтиСтроки() и Удалить() позволяет удалить все строки по отбору без явного цикла перебора всей таблицы:

Отбор = Новый Структура("Количество", 0);

НайденныеСтроки = ТаблицаЗначений.НайтиСтроки(Отбор);

Для Каждого Строка Из НайденныеСтроки Цикл

ТаблицаЗначений.Удалить(Строка);

КонецЦикла;

НайтиСтроки возвращает массив ссылок на строки, и этот массив не меняется при удалении — поэтому удалять можно в прямом порядке. Если условие сложное и не сводится к равенству значений, выгрузите таблицу в запрос через временную таблицу, отфильтруйте её запросом и загрузите результат обратно методом Загрузить(). На больших объёмах это работает заметно быстрее построчного перебора.

Удаление строк в динамическом списке и на форме

Отдельная ситуация — попытка удалить строки динамического списка на управляемой форме в цикле. Динамический список — это только отображение результата запроса, а не редактируемая коллекция. Строки из него «удалить в цикле» нельзя в принципе: нужно программно получать ссылки на объекты и удалять сами объекты через УдалитьОбъект() или установку пометки удаления.

Выборка = Справочники.Номенклатура.Выбрать();

Пока Выборка.Следующий() Цикл

Если НужноУдалить(Выборка.Ссылка) Тогда

ОбъектДляУдаления = Выборка.Ссылка.ПолучитьОбъект();

ОбъектДляУдаления.УстановитьПометкуУдаления(Истина);

КонецЕсли;

КонецЦикла;

Здесь перебирается выборка из базы, а не изменяемая коллекция, поэтому пометка удаления внутри цикла безопасна. Однако массовое удаление объектов — операция с последствиями: ссылки на удалённые элементы останутся в документах и регистрах. Для справочников и документов почти всегда правильнее ставить пометку удаления, а физическое удаление выполнять штатной обработкой «Удаление помеченных объектов» с контролем ссылочной целостности.

Порядок действий
  1. 1
    Определите тип коллекции: табличная часть объекта, таблица значений или выборка из базы
  2. 2
    Для табличной части и таблицы значений выберите обратный обход или массив к удалению
  3. 3
    Для большой таблицы значений со сложным условием используйте запрос с временной таблицей
  4. 4
    Протестируйте код на копии базы с реальным объёмом данных
  5. 5
    Проверьте результат: количество оставшихся строк и отсутствие пропущенных строк по условию

Проверка результата и контроль ошибок

После доработки кода удаления полезно добавить контрольный подсчёт: до обработки запомнить количество строк, после — сравнить с ожидаемым числом удалённых. Расхождение — признак того, что условие сработало не так, как задумано, или часть строк была пропущена.

Для проверки корректности прямого обхода (если он всё же используется по историческим причинам) достаточно теста на таблице, где подряд идут три-четыре строки, удовлетворяющие условию удаления. Именно соседние строки вскрывают ошибку смещения индексов: при прямом цикле вторая из соседних строк гарантированно останется.

Контрольные точки

Как проверить результат

Количество строк после обработки
Норма

Равно исходному минус число строк по условию

Тревожный признак

Строк больше ожидаемого — часть пропущена из-за смещения индексов

Соседние строки, подходящие под условие
Норма

Удалены все подряд идущие строки

Тревожный признак

Остаётся каждая вторая строка — признак прямого цикла с удалением

Протокол или сообщения
Норма

Число удалённых строк совпадает с расчётным

Тревожный признак

Расхождение без объяснимой причины

Типичные ошибки и частые вопросы

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

Типичные ошибки
01
Удаление в цикле «Для Каждого» — строки пропускаются из-за смещения индексов
02
Условие «Пока Индекс > 0» вместо «>= 0» — нулевая строка никогда не обрабатывается
03
Передача в Удалить() скопированной структуры вместо строки коллекции — строка не находится
04
Попытка удалить строки динамического списка вместо удаления объектов в базе
05
Физическое удаление объектов без контроля ссылочной целостности — битые ссылки в документах
Можно ли удалять строки в цикле «Для Каждого», если сразу выходить через Прервать?

Да, если удаляется ровно одна строка и цикл сразу прерывается, это безопасно: коллекция изменилась, но дальнейший перебор не выполняется. Такой приём типичен для поиска и удаления первой подходящей строки. Для удаления нескольких строк он не подходит.

Что быстрее: обратный обход или массив к удалению?

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

Как удалить строки табличной части по сложному условию с запросом?

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

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

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

Как безопасно удалить много объектов из базы в цикле?

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

Работают ли эти приёмы в обычных и управляемых формах одинаково?

Да, методы коллекций (Удалить, НайтиСтроки, Количество) и приёмы обратного обхода одинаковы на уровне встроенного языка независимо от типа клиента. Различия касаются только контекста выполнения: на тонком клиенте недоступны некоторые серверные операции, поэтому обработку больших таблиц лучше выполнять в серверных процедурах.

Итог

Что важно запомнить

Никогда не удаляйте строки в прямом цикле перебора коллекции. Используйте обратный обход с конца, массив строк к удалению или фильтрацию через запрос для больших таблиц значений. Объекты из базы удаляйте через пометку удаления с последующим контролем ссылочной целостности, и проверяйте результат на тестовой копии.