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

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

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

В чем заключается корень проблемы?

Проблема кроется в механизме работы коллекции. Когда мы используем классический цикл Для Каждого или перебор по индексу от 0 до Количество() - 1, удаление строки физически сдвигает все последующие строки вверх. Если мы удалили строку с индексом 5, то строка, которая была шестой, моментально становится пятой. Однако счетчик цикла в следующей итерации переходит к числу 6, фактически пропуская ту строку, которая только что переместилась на место удаленной.

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

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

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

Проанализируем пример кода для этого метода:


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

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

Способ 2. Использование вспомогательного массива

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

Разберем, как это реализовать:


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

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

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

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

Способ 3. Цикл "С начала" с коррекцией индекса

Если по каким-то причинам вам принципиально обходить таблицу с начала, необходимо модифицировать алгоритм так, чтобы счетчик индекса увеличивался только в том случае, если строка не была удалена.

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


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

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

Способ 4. Метод НайтиСтроки (для простых условий)

Если условие удаления — это простое равенство (например, нужно удалить все строки, где склад — "Основной"), можно воспользоваться встроенным методом НайтиСтроки(). Этот метод работает крайне быстро, так как реализован на уровне платформы.


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

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

Выясним причину высокой скорости этого метода: НайтиСтроки использует внутренние алгоритмы платформы (на языке C++), что значительно быстрее, чем перебор каждой строки в интерпретируемом коде 1С. Однако помните, что НайтиСтроки не поддерживает сложные условия (например, "больше" или "не равно").

Способ 5. Копирование вместо удаления (для больших таблиц)

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

Проанализируем этот подход:

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

Пример использования Скопировать для инвертированного условия:


// Оставляем только те строки, где Сумма > 0
// (Если условие позволяет выразить его через структуру отбора)
ПараметрыОтбора = Новый Структура("ПометкаУдаления", Ложь);
ТаблицаРезультат = ТаблицаИсточник.Скопировать(ПараметрыОтбора);

Вопросы производительности и "сборки мусора"

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

Итоги

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

← На главную