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

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

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

Почему сбивается выборка при обычном обходе (выясняем причину)

Рассмотрим стандартный, но ошибочный код, который часто пишут начинающие разработчики:


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

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

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

  1. Индекс 0: Товар 1 (Флаг = Ложь)
  2. Индекс 1: Товар 2 (Флаг = Истина)
  3. Индекс 2: Товар 3 (Флаг = Истина)
  4. Индекс 3: Товар 4 (Флаг = Ложь)

Запускаем цикл:

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

Именно из-за этого физического сдвига элементов в оперативной памяти и «сбивается» выборка. Чтобы избежать этой логической ошибки, необходимо использовать специальные приемы обхода коллекции. Ниже мы разберем четыре наиболее эффективных и безопасных способа.

Способ №1: Удаление через предварительное накопление строк в массив

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

Разберем этот алгоритм по шагам на примере следующего кода:


// Шаг 1. Создаем пустой массив для хранения строк, предназначенных для удаления
МассивУдаляемыхСтрок = Новый Массив;

// Шаг 2. Заполняем массив нужными строками без изменения исходной таблицы в процессе обхода
Для каждого СтрокаТаблицы Из Товары Цикл
    Если СтрокаТаблицы.Флаг = Истина Тогда
        МассивУдаляемыхСтрок.Добавить(СтрокаТаблицы);
    КонецЕсли;
КонецЦикла;

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

Преимущества этого способа:

Способ №2: Обратный обход коллекции (цикл с конца к началу)

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

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


// Определяем индекс последнего элемента коллекции
Индекс = Товары.Количество() - 1;

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

Также можно встретить альтернативный вариант реализации обратного цикла с использованием математического преобразования индексов в стандартном цикле Для:


КоличествоСтрок = Товары.Количество();
Для Счетчик = 1 По КоличествоСтрок Цикл
    // Вычисляем индекс с конца таблицы
    ТекущийИндекс = КоличествоСтрок - Счетчик;
    СтрокаТаблицы = Товары[ТекущийИндекс];
    
    Если СтрокаТаблицы.Флаг = Истина Тогда
        Товары.Удалить(СтрокаТаблицы);
    КонецЕсли;
КонецЦикла;

Преимущества этого способа:

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

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

Рассмотрим пример реализации этого подхода:


// Создаем структуру отбора с нужными нам критериями
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Флаг", Истина);

// Получаем массив строк, соответствующих условию отбора
НайденныеСтроки = Товары.НайтиСтроки(ПараметрыОтбора);

// Удаляем полученные строки из коллекции
Для каждого ТекущаяСтрока Из НайденныеСтроки Цикл
    Товары.Удалить(ТекущаяСтрока);
КонецЦикла;

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

Способ №4: Копирование таблицы с отбором (метод "от обратного")

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

Для этого мы можем использовать метод Скопировать(), передав в него структуру отбора с противоположным условием (то есть отбираем строки, у которых Флаг = Ложь):


// Задаем отбор для строк, которые мы хотим ОСТАВИТЬ в таблице
ПараметрыОтбора = Новый Структура("Флаг", Ложь);

// Копируем только подходящие строки в новую временную таблицу значений
ВременнаяТаблица = Товары.Скопировать(ПараметрыОтбора);

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

Важное примечание: Если вы работаете с объектом ТаблицаЗначений (а не с табличной частью документа), то код сокращается до одной элегантной строки:


МояТаблицаЗначений = МояТаблицаЗначений.Скопировать(Новый Структура("Флаг", Ложь));

Сравнительный анализ производительности решений

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

  1. Малый объем удалений (удаляется до 15% строк): Рекомендуется использовать Способ №1 (Массив) или Способ №3 (НайтиСтроки). Они не требуют пересоздания структуры таблицы и выполняются достаточно быстро без избыточного расхода памяти.
  2. Большой объем удалений (удаляется более 30% строк): В этом случае поштучное удаление методом Удалить() становится неэффективным. При каждом удалении строки платформа сдвигает все последующие элементы в памяти, что приводит к квадратичной сложности алгоритма. Здесь намного быстрее отработает Способ №4 (Копирование "от обратного"). Платформа один раз выделит непрерывный блок памяти под новую отфильтрованную таблицу, что сэкономит ресурсы сервера.

Особенности работы с табличными частями на управляемых формах

При переносе кода на управляемые формы (работа с типом ДанныеФормыКоллекция) возникают дополнительные интерфейсные нюансы, о которых важно помнить:

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

← На главную