При работе с объектом ТаблицаЗначений начинающие разработчики часто сталкиваются с логической ошибкой: при попытке удалить строки в цикле "сверху вниз" (от первой к последней) часть строк пропускается, а программа может выдать ошибку обращения по несуществующему индексу. Рассмотрим, почему это происходит, и проанализируем самые эффективные способы решения этой задачи.
Проблема кроется в механизме работы коллекции. Когда мы используем классический цикл Для Каждого или перебор по индексу от 0 до Количество() - 1, удаление строки физически сдвигает все последующие строки вверх. Если мы удалили строку с индексом 5, то строка, которая была шестой, моментально становится пятой. Однако счетчик цикла в следующей итерации переходит к числу 6, фактически пропуская ту строку, которая только что переместилась на место удаленной.
Для решения этой задачи в среде 1С существует несколько проверенных подходов. Разберем их по шагам, двигаясь от самых простых к наиболее производительным.
Это самый популярный и надежный метод. Если мы начнем обходить таблицу с последней строки и будем двигаться к первой, то удаление текущей строки никак не повлияет на индексы тех строк, которые нам еще только предстоит обработать (они находятся "выше" по списку).
Проанализируем пример кода для этого метода:
Индекс = ТаблицаДанных.Количество() - 1;
Пока Индекс >= 0 Цикл
СтрокаТЗ = ТаблицаДанных[Индекс];
Если УсловиеДляУдаления(СтрокаТЗ) Тогда
ТаблицаДанных.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
В данном примере мы используем цикл Пока, вручную управляя индексом. Важно, что ТаблицаДанных.Количество() вычисляется один раз в начале или при каждом шаге, но так как мы уменьшаем индекс, мы никогда не выйдем за границы коллекции.
Если вам визуально удобнее работать с циклом Для каждого, можно применить двухэтапный подход. Сначала мы найдем все строки, которые подлежат удалению, и сохраним ссылки на них в отдельный массив. Затем вторым циклом пройдемся по массиву и удалим эти строки из таблицы.
Разберем, как это реализовать:
УдаляемыеСтроки = Новый Массив;
// Этап 1: Поиск строк по условию
Для Каждого ТекущаяСтрока Из ТаблицаДанных Цикл
Если ТекущаяСтрока.Сумма = 0 Тогда
УдаляемыеСтроки.Добавить(ТекущаяСтрока);
КонецЕсли;
КонецЦикла;
// Этап 2: Физическое удаление
Для Каждого СтрокаДляУдаления Из УдаляемыеСтроки Цикл
ТаблицаДанных.Удалить(СтрокаДляУдаления);
КонецЦикла;
Важный нюанс: Метод Удалить() в объекте ТаблицаЗначений умеет принимать как индекс (число), так и саму строку (объект СтрокаТаблицыЗначений). Использование массива ссылок избавляет нас от необходимости следить за индексами вообще.
Если по каким-то причинам вам принципиально обходить таблицу с начала, необходимо модифицировать алгоритм так, чтобы счетчик индекса увеличивался только в том случае, если строка не была удалена.
Посмотрим на пример реализации:
Сч = 0;
Пока Сч < ТаблицаДанных.Количество() Цикл
Строка = ТаблицаДанных[Сч];
Если НужноУдалить(Строка) Тогда
ТаблицаДанных.Удалить(Сч);
// Индекс не увеличиваем, так как на место этой строки встала следующая
Иначе
Сч = Сч + 1;
КонецЕсли;
КонецЦикла;
Этот метод работает правильно, но считается менее "чистым" с точки зрения стиля программирования, чем обход с конца.
Если условие удаления — это простое равенство (например, нужно удалить все строки, где склад — "Основной"), можно воспользоваться встроенным методом НайтиСтроки(). Этот метод работает крайне быстро, так как реализован на уровне платформы.
ПараметрыОтбора = Новый Структура("Статус", "Неактивен");
СтрокиДляУдаления = ТаблицаДанных.НайтиСтроки(ПараметрыОтбора);
Для Каждого Стр Из СтрокиДляУдаления Цикл
ТаблицаДанных.Удалить(Стр);
КонецЦикла;
Выясним причину высокой скорости этого метода: НайтиСтроки использует внутренние алгоритмы платформы (на языке C++), что значительно быстрее, чем перебор каждой строки в интерпретируемом коде 1С. Однако помните, что НайтиСтроки не поддерживает сложные условия (например, "больше" или "не равно").
В некоторых ситуациях, когда в таблице миллионы строк, а удалить нужно большую часть, эффективнее будет не удалять лишнее, а создать новую таблицу и скопировать в нее только то, что нужно оставить.
Проанализируем этот подход:
Скопировать() с параметром отбора, чтобы получить новую таблицу с нужными строками.СкопироватьКолонки() и в цикле заполняем её методом ЗаполнитьЗначенияСвойств() для подходящих строк.Пример использования Скопировать для инвертированного условия:
// Оставляем только те строки, где Сумма > 0
// (Если условие позволяет выразить его через структуру отбора)
ПараметрыОтбора = Новый Структура("ПометкаУдаления", Ложь);
ТаблицаРезультат = ТаблицаИсточник.Скопировать(ПараметрыОтбора);
В процессе обсуждения на форумах часто возникают споры о том, как часто вызывается метод Количество() и насколько затратно само удаление. Важно понимать следующее:
Количество() в 1С возвращает уже готовое значение из свойств объекта, он не пересчитывает таблицу заново каждый раз.НайтиСтроки или создание новой таблицы будет предпочтительнее.ТаблицаЗначений в коде, обработать их, а затем загрузить обратно в реквизит формы методом Загрузить().Мы проанализировали различные ситуации и выяснили, что универсального "лучшего" способа не существует — всё зависит от объема данных и сложности условий. Для повседневных задач обход с конца является "золотым стандартом", обеспечивающим понятность кода и стабильный результат.