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

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

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

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

Решение 1. Классический обратный обход (удаление с конца)

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

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


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

Этот способ является универсальным стандартом разработки. Мы начинаем обход с максимального индекса ТЗ.Количество() - 1 и двигаемся к нулю, декрементируя счетчик на каждом шаге.

Решение 2. Прямой обход с условным смещением индекса

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

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


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

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

Решение 3. «Лайфхак» с отрицательным индексом в цикле Для

Существует изящный способ запустить быстрый платформенный цикл Для в обратную сторону без использования конструкции Пока. Мы можем математически инвертировать счетчик цикла. Рассмотрим этот необычный прием:


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

Выясним, как работает эта математика. Если в нашей таблице 10 строк, то переменная Инд будет последовательно принимать значения от -9 до 0 с шагом +1. Внутри тела цикла мы обращаемся к строке по индексу -Инд. На первом шаге это будет -(-9) = 9, на втором -(-8) = 8, и так далее до 0. Это позволяет использовать лаконичный цикл Для, сохраняя производительность и избавляясь от ручного управления счетчиком.

Решение 4. Оптимизация удаления для Табличных Частей документов

Если перед нами стоит задача обработать не просто независимую таблицу значений в оперативной памяти, а реальную табличную часть (ТЧ) документа на форме, построчное удаление через Удалить() напрямую из коллекции ТЧ будет крайне неэффективным. Причина кроется во внутренних механизмах платформы 1С:

  1. При каждом построчном удалении из реальной табличной части платформа автоматически инициирует пересчет итогов документа (сумм, налогов, валют, веса).
  2. Если удаление происходит на форме, интерфейс пытается мгновенно отреагировать и обновить связанные элементы экрана, что приводит к задержкам отрисовки.
  3. Перестройка внутренних индексов базы данных для каждой строки нагружает рабочий процесс сервера.

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

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


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

// Шаг 2. Выполняем быстрое удаление в памяти (например, обратным обходом)
Индекс = БуфернаяТЗ.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если БуфернаяТЗ[Индекс].Количество = 0 Тогда
        БуфернаяТЗ.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

// Шаг 3. Загружаем отфильтрованную таблицу обратно в объект
ЭтотОбъект.Товары.Загрузить(БуфернаяТЗ);

При использовании метода Загрузить() вся коллекция перезаписывается мгновенно. Пересчет итоговых полей документа и обновление экранных форм произойдет ровно один раз в самом конце, что увеличивает скорость работы алгоритма в десятки раз на объемах данных более 100 строк.

Решение 5. Полный отказ от удаления в пользу копирования

Если условие фильтрации достаточно простое, а удалению подлежит значительная часть таблицы (более 30-50% строк), самым быстрым решением будет не удаление ненужных строк, а перенос нужных строк в новую таблицу значений с помощью платформенных методов, написанных на низкоуровневом языке C++.

Рассмотрим вариант с использованием метода Скопировать(). Если критерием фильтрации является простое равенство реквизита какому-то значению, мы можем получить итоговую таблицу без циклов вообще:


// Отбираем только нужные строки, которые должны ОСТАТЬСЯ в таблице
Отбор = Новый Структура("Статус", "Активен");
НоваяТЗ = ТЗ.Скопировать(Отбор);

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


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

Для Каждого СтрокаТЗ Из ТЗ Цикл
    // Сложное условие, по которому строка должна быть сохранена
    Если СтрокаТЗ.Количество > 0 И СтрокаТЗ.Цена > 100 Тогда
        МассивОставляемыхСтрок.Добавить(СтрокаТЗ);
    КонецЕсли;
КонецЦикла;

// Платформа мгновенно создает новую ТЗ, содержащую только нужные строки
НоваяТЗ = ТЗ.Скопировать(МассивОставляемыхСтрок);

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

← На главную