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

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

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

Выясним причину ошибки: почему строки пропускаются

Посмотрим на типичный фрагмент кода, приводящий к ошибке:


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

В этом примере кроются сразу две критические проблемы:

  1. Смещение элементов в итерируемой коллекции. Цикл Для Каждого последовательно перемещает внутренний указатель с позиции 0 на позицию 1, затем 2 и так далее. Когда вызывается метод Удалить(), все последующие строки сдвигаются на одну позицию к началу коллекции (вверх). В результате строка, следовавшая непосредственно за удаленной, занимает ее индекс. Цикл же переходит к следующему шагу и тем самым перескакивает через эту сместившуюся строку. Если несколько строк подряд подлежат удалению, ровно половина из них останется в таблице.
  2. Некорректная передача НомерСтроки вместо индекса. Свойство НомерСтроки табличной части нумеруется с 1, тогда как программный индекс строк в коллекциях 1С начинается с 0. Метод Удалить() может принимать либо объект самой строки, либо целочисленный индекс (от 0 до Количество - 1). Передача НомерСтроки приводит к удалению строки со сдвигом на +1, а на последних строках вызывает исключение выхода индекса за границы массива.

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

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

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


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

// Первый проход: собираем ссылки на строки, удовлетворяющие условию
Для Каждого Стр Из ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения Цикл
    Если Стр.Свойство.ДляНоменклатурыТипХарактеристики <> Ссылка.ТипХарактеристики Тогда
        МассивУдаляемых.Добавить(Стр);
    КонецЕсли;
КонецЦикла;

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

Данный подход нагляден, прост в отладке и полностью исключает проблемы со смещением индексов при обходе.

Способ 2. Обратный обход по индексу через цикл Пока

Рассмотрим подробнее альтернативное решение — обход коллекции с конца к началу. Когда мы удаляем элементы с конца таблицы, сдвиг нижележащих строк уже никак не влияет на индексы тех элементов, которые расположены ближе к началу и еще не были проверены.

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


Таблица = ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения;
Индекс = Таблица.Количество() - 1;

Пока Индекс >= 0 Цикл
    Стр = Таблица[Индекс];
    Если Стр.Свойство.ДляНоменклатурыТипХарактеристики <> Ссылка.ТипХарактеристики Тогда
        Таблица.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Обратите внимание: доступ к строке осуществляется через квадратные скобки по индексу Таблица[Индекс], а в метод Удалить() передается текущее значение переменной Индекс.

Способ 3. Прямой обход с условным инкрементом индекса

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

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


Таблица = ОбработкаОбъектЗначенияСвойств.СвойстваИЗначения;
Индекс = 0;

Пока Индекс < Таблица.Количество() Цикл
    Стр = Таблица[Индекс];
    Если Стр.Свойство.ДляНоменклатурыТипХарактеристики <> Ссылка.ТипХарактеристики Тогда
        Таблица.Удалить(Индекс);
    Иначе
        Индекс = Индекс + 1;
    КонецЕсли;
КонецЦикла;

Альтернативные оптимизированные подходы

Проанализируем производительность при работе с большими объемами данных. Метод Удалить() при каждом вызове физически сдвигает массив строк в памяти. Если таблица содержит тысячи строк и удаляется значительная их часть, постоянные вызовы Удалить() могут существенно замедлить выполнение операции.

Для ускорения работы применимы следующие практики:

  1. Фильтрация на этапе выборки. Если данные выбираются запросом из базы данных, условие соответствия ТипХарактеристики правильнее наложить непосредственно в секции ГДЕ текста запроса. В таком случае ненужные данные вовсе не будут считываться в память и отпадет необходимость их построчного удаления.
  2. Использование метода НайтиСтроки(). Если условие удаления представляет собой точное равенство конкретному значению колонки, эффективнее найти строки методом НайтиСтроки(), передав структуру отбора, и затем в цикле удалить только найденные позиции.
  3. Перезаполнение коллекции. При удалении большого процента строк быстрее отобрать нужные строки в новую таблицу методом Скопировать(), очистить исходную коллекцию методом Очистить() и загрузить отфильтрованные данные обратно.
← На главную