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

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

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

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

Выясняем причину: почему «перескакивает» индекс?

Давайте разберем по шагам, что происходит в оперативной памяти компьютера, когда мы пишем самый простой (и ошибочный) цикл обхода коллекции:


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

Представим, что в табличной части Товары содержится 4 строки с индексами от 0 до 3, и нам нужно удалить строки, где сумма равна нулю (допустим, это строки с индексами 1 и 2):

  1. На первой итерации цикл анализирует строку с индексом 0. Условие не выполняется, переходим дальше.
  2. На второй итерации анализируется строка с индексом 1. Сумма равна 0. Платформа выполняет метод Удалить() для этой строки.
  3. Внимание, сдвиг! Как только строка с индексом 1 удаляется, все последующие строки автоматически сдвигаются вверх. Бывшая строка с индексом 2 теперь получает индекс 1, а бывшая строка с индексом 3 становится строкой с индексом 2.
  4. На третьей итерации внутренний счетчик цикла увеличивается и переходит к индексу 2. Но на позиции с индексом 2 теперь находится бывшая строка 3! А та строка, которая переместилась на позицию 1, просто пропускается («перепрыгивается») и никогда не будет проверена.

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

Способ 1. Обход коллекции с конца (Обратный цикл)

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

Давайте посмотрим на практическую реализацию этого метода:


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

Преимущества метода:

Способ 2. Предварительный сбор строк в массив

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

Посмотрим, как выглядит такой код:


МассивУдаляемыхСтрок = Новый Массив;

// Этап 1: Поиск и фиксация строк
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
    Если СтрокаТЧ.Сумма = 0 Тогда
        МассивУдаляемыхСтрок.Добавить(СтрокаТЧ);
    КонецЕсли;
КонецЦикла;

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

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

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

В тех случаях, когда условие удаления является простым равенством (например, нужно удалить все строки, где Сумма строго равна 0, или где реквизит Номенклатура пуст), платформа 1С предоставляет готовый быстрый инструмент — метод НайтиСтроки().

Этот метод возвращает массив строк, соответствующих структуре поиска. Мы можем сразу же запустить цикл удаления по этому массиву:


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

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

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

Способ 4. Обход с начала с управляемым шагом (инкремент по условию)

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

Реализуем этот алгоритм в коде:


Индекс = 0;
Пока Индекс < Объект.Товары.Количество() Цикл
    СтрокаТЧ = Объект.Товары[Индекс];
    Если СтрокаТЧ.Сумма = 0 Тогда
        Объект.Товары.Удалить(Индекс); // Индекс не меняем, проверяем элемент, вставший на это место
    Иначе
        Индекс = Index + 1; // Переходим к следующему элементу
    КонецЕсли;
КонецЦикла;

Способ 5. Оптимизированный метод для больших таблиц (Пересоздание таблицы)

Когда табличная часть документа содержит десятки тысяч строк, поочередное удаление элементов в цикле может вызывать серьезные просадки производительности. При каждом вызове метода Удалить() система вынуждена перестраивать индексы коллекции в оперативной памяти.

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

Проанализируем этот высокопроизводительный вариант:


// Шаг 1: Выгружаем табличную часть во временную Таблицу Значений
ТаблицаОставшихся = Объект.Товары.Выгрузить();

// Шаг 2: Создаем массив строк, которые нужно удалить (Сумма = 0)
ПараметрыОтбора = Новый Структура("Сумма", 0);
СтрокиДляУдаления = ТаблицаОставшихся.НайтиСтроки(ПараметрыОтбора);

// Шаг 3: Удаляем строки из временной таблицы (это происходит быстрее)
Для Каждого СтрокаДляУдаления Из СтрокиДляУдаления Цикл
    ТаблицаОставшихся.Удалить(СтрокаДляУдаления);
КонецЦикла;

// Шаг 4: Очищаем исходную ТЧ и загружаем отфильтрованные данные
Объект.Товары.Очистить();
Объект.Товары.Загрузить(ТаблицаОставшихся);

Важный нюанс: Клиент-Серверное взаимодействие

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

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

← На главную