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

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

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

Почему нельзя удалять строки прямым циклом «Для Каждого»

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


// ОШИБОЧНЫЙ КОД!
Сч = 0;
Для Каждого Стр Из ТаблицаДанных Цикл
    Если Стр.Реквизит <> "Значение" Тогда
        ТаблицаДанных.Удалить(Сч);
    КонецЕсли;
    Сч = Сч + 1;
КонецЦикла;

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

  1. Смещение индексов: как только из коллекции удаляется строка с индексом 0, строка с индексом 1 автоматически становится строкой с индексом 0. Счетчик же на следующем шаге увеличивается и обращается к индексу 1 (который изначально был вторым элементом). В результате одна строка неизбежно пропускается без проверки.
  2. Нарушение итератора коллекции: прямое удаление элементов из коллекции во время выполнения цикла Для Каждого нарушает внутренний указатель обхода, что приводит к непредсказуемому поведению и пропуску элементов.
  3. Разделение данных и интерфейса: элемент формы (табличное поле) сам по себе является лишь средством визуального отображения. Манипуляции по удалению строк всегда производятся над источником данных (например, объектом ТаблицаЗначений или ДанныеФормыКоллекция), после чего интерфейс обновляется.

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

Способ 1. Обход коллекции с конца по убыванию индексов

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

Посмотрим на пример реализации с помощью цикла Для:


ВсегоСтрок = ТаблицаДанных.Количество();
Для Индекс = 1 По ВсегоСтрок Цикл
    ТекущийИндекс = ВсегоСтрок - Индекс;
    СтрокаТаблицы = ТаблицаДанных.Получить(ТекущийИндекс);
    
    Если СтрокаТаблицы.Реквизит <> "Значение" Тогда
        ТаблицаДанных.Удалить(ТекущийИндекс);
    КонецЕсли;
КонецЦикла;

Также этот алгоритм можно изящно записать с использованием отрицательного диапазона счетчика:


ВсегоСтрок = ТаблицаДанных.Количество();
Для ОтрицательныйИндекс = -ВсегоСтрок + 1 По 0 Цикл
    ТекущийИндекс = -ОтрицательныйИндекс;
    СтрокаТаблицы = ТаблицаДанных[ТекущийИндекс];
    
    Если СтрокаТаблицы.Реквизит <> "Значение" Тогда
        ТаблицаДанных.Удалить(ТекущийИндекс);
    КонецЕсли;
КонецЦикла;

Способ 2. Обход с помощью цикла «Пока» с ручным управлением индексом

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

Разберем пример реализации:


Индекс = 0;
Пока Индекс < ТаблицаДанных.Количество() Цикл
    СтрокаТаблицы = ТаблицаДанных[Индекс];
    
    Если СтрокаТаблицы.Реквизит <> "Значение" Тогда
        ТаблицаДанных.Удалить(СтрокаТаблицы);
    Иначе
        Индекс = Индекс + 1;
    КонецЕсли;
КонецЦикла;

Способ 3. Двухэтапное удаление через массив ссылок на строки

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

Рассмотрим реализацию данного шаблона:


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

// Этап 1. Накопление удаляемых строк
Для Каждого Стр Из ТаблицаДанных Цикл
    Если Стр.Реквизит <> "Значение" Тогда
        СтрокиКУдалению.Добавить(Стр);
    КонецЕсли;
КонецЦикла;

// Этап 2. Удаление строк из таблицы
Для Каждого УдаляемаяСтрока Из СтрокиКУдалению Цикл
    ТаблицаДанных.Удалить(УдаляемаяСтрока);
КонецЦикла;

Преимущества двухэтапного подхода:

  1. Высокая читаемость и сопровождаемость кода.
  2. Возможность параллельно с поиском выполнять другие расчеты над строками в прямом порядке.
  3. Метод Удалить() у ТаблицыЗначений умеет принимать как индекс, так и саму строку таблицы в качестве параметра.

Способ 4. Использование метода «НайтиСтроки» для условий точного равенства

Если условие удаления заключается в поиске точного совпадения реквизита (например, очистка строк с нулевым количеством или определенным статусом), циклы с ручной проверкой условий не требуются. Встроенный метод НайтиСтроки() отработает быстрее и лаконичнее.

Посмотрим на пример:


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

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

Ограничение метода: НайтиСтроки() выполняет поиск исключительно по строгому равенству. Условия типа «не равно», «больше», «меньше» или вхождение в список данным методом обработать нельзя.

Особенности производительности и работа с большими таблицами

Проанализируем важные нюансы оптимизации, которые необходимо учитывать при работе со значительными объемами данных:

  1. Сложность последовательного удаления: при каждом вызове ТаблицаДанных.Удалить() платформа сдвигает все нижележащие строки в памяти. Если таблица содержит десятки тысяч записей и удаляется значительная их часть, операция превращается в квадратичную сложность $O(N^2)$, что приводит к заметным зависаниям.
  2. Пересоздание таблицы вместо построчного удаления: если строк много и удаляется большая их часть, гораздо быстрее отфильтровать нужные строки в новую таблицу (через СкопироватьКолонки() и перенос подходящих строк) или применить объект Запрос с временной таблицей, а затем загрузить результат обратно через метод Загрузить().
  3. Полная очистка: если по условию алгоритма требуется очистить абсолютно всю коллекцию, всегда используйте метод ТаблицаДанных.Очистить() вместо циклического перебора.
  4. Управляемые формы: если работа идет с реквизитом управляемой формы (ДанныеФормыКоллекция), построчное удаление на клиенте вызывает лишние пересчеты интерфейса. Оптимальный шаблон — вызов сервера, конвертация через РеквизитФормыВЗначение() в объект ТаблицаЗначений, быстрая обработка и обратная загрузка через ЗначениеВРеквизитФормы().
← На главную