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

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

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

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

Выясняем причину: почему ломается простой цикл «Для Каждого»?

Давайте проанализируем ситуацию, с которой столкнулся автор темы на форуме. Он попытался обойти строки таблицы с помощью стандартного цикла Для Каждого и удалить элементы по условию:


Для Каждого стр из Объект.Таблица Цикл
    Если стр.Цена < 1000 Тогда
        индекс = стр.ПолучитьИдентификатор();
        Объект.Таблица.Удалить(индекс);
    КонецЕсли;    
КонецЦикла;

Этот код работает некорректно. Чтобы понять причину, разберем поведение платформы «под капотом» по шагам:

  1. Коллекция ДанныеФормыКоллекция (именно этот тип имеет реквизит формы Объект.Таблица) хранит элементы последовательно, и каждый элемент имеет свой внутренний индекс (позицию), начиная с нуля.
  2. Когда мы запускаем цикл Для Каждого, платформа настраивает внутренний указатель на начало коллекции.
  3. Предположим, у нас есть три строки с индексами 0, 1 и 2. Строки 0 и 1 соответствуют условию удаления (цена меньше 1000).
  4. На первой итерации цикла мы удаляем строку с индексом 0.
  5. Что происходит в этот момент с коллекцией? Все оставшиеся строки автоматически сдвигаются вверх! Строка, которая была на позиции 1, теперь получает индекс 0. Строка с бывшим индексом 2 получает индекс 1.
  6. На второй итерации цикла внутренний указатель платформы переходит к следующему шагу — он ожидает обработать элемент с индексом 1.
  7. В итоге бывшая строка 1 (которая теперь имеет индекс 0) полностью пропускается алгоритмом и не проверяется на соответствие условию. Платформа сразу переходит к бывшей строке 2 (новый индекс 1).

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

Способ №1: Обратный цикл (удаление элементов с конца)

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

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

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


&НаСервере
Процедура ОчиститьНаСервере()
    
    // Получаем индекс последнего элемента таблицы
    Индекс = Объект.Таблица.Количество() - 1;
    
    // Двигаемся в обратном направлении до нулевого элемента
    Пока Индекс >= 0 Цикл
        
        СтрокаТаблицы = Объект.Таблица[Индекс];
        
        // Проверяем наше произвольное условие
        Если СтрокаТаблицы.Цена < 1000 Тогда
            Объект.Таблица.Удалить(Индекс);
        КонецЕсли;
        
        // Уменьшаем индекс на единицу для перехода к предыдущей строке
        Индекс = Индекс - 1;
        
    КонецЦикла;
    
КонецПроцедуры

Преимущества метода: Высокая скорость работы на небольших таблицах и отсутствие необходимости выделять дополнительную память под промежуточные объекты.

Способ №2: Предварительное накопление строк в массив

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

Этот способ элегантно обходит проблему изменения индексов за счет того, что метод Удалить() коллекции ДанныеФормыКоллекция умеет принимать в качестве параметра не только числовой индекс, но и саму строку (объект типа ДанныеФормыЭлемент). Платформа 1С самостоятельно вычисляет актуальный индекс переданной строки на момент удаления.

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


&НаСервере
Процедура ОчиститьЧерезМассивНаСервере()
    
    // Создаем временный массив для хранения ссылок на удаляемые строки
    МассивУдаляемыхСтрок = Новый Массив;
    
    // Шаг 1. Находим все строки по условию и добавляем их в массив
    Для Каждого ТекущаяСтрока Из Объект.Таблица Цикл
        Если ТекущаяСтрока.Цена < 1000 Тогда
            МассивУдаляемыхСтрок.Добавить(ТекущаяСтрока);
        КонецЕсли;
    КонецЦикла;
    
    // Шаг 2. Удаляем собранные строки из исходной таблицы
    Для Каждого СтрокаДляУдаления Из МассивУдаляемыхСтрок Цикл
        Объект.Таблица.Удалить(СтрокаДляУдаления);
    КонецЦикла;
    
КонецПроцедуры

Почему этот способ хорош: Код невероятно прост в чтении и сопровождении. Вам не нужно вручную контролировать счетчики и индексы, что сводит вероятность совершить ошибку на «минус один» к нулю.

Способ №3: Оптимальное решение для больших таблиц через СУБД

Описанные выше способы отлично работают, если в вашей таблице содержится до нескольких сотен или тысяч строк. Но что делать, если строк десятки тысяч? Построчное удаление в цикле на больших объемах данных будет работать крайне медленно. При каждом удалении строки операционная система вынуждена перестраивать коллекцию в оперативной памяти компьютера, из-за чего сложность работы программы растет в геометрической прогрессии.

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

Проанализируем этот производительный алгоритм:


&НаСервере
Процедура ОчиститьБыстроНаСервере()
    
    // 1. Конвертируем коллекцию формы в объект "ТаблицаЗначений"
    ТаблицаДанных = РеквизитФормыВЗначение("Объект.Таблица");
    
    // 2. Используем запрос для быстрой выборки только НУЖНЫХ строк
    // Вместо удаления лишнего, мы просто отберем те строки, которые должны ОСТАТЬСЯ
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ИсходныеДанные ИЗ &ТаблицаДанных КАК ТаблицаДанных;
        |
        |ВЫБРАТЬ * ИЗ ВТ_ИсходныеДанные ГДЕ Цена >= 1000"; // Оставляем строки с ценой 1000 и выше
        
    Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
    
    // 3. Выгружаем результат запроса в новую чистую таблицу значений
    ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
    
    // 4. Загружаем отфильтрованные данные обратно в реквизит управляемой формы
    ЗначениеВРеквизитФормы(ТаблицаРезультат, "Объект.Таблица");
    
КонецПроцедуры

Важное примечание: Обратите внимание на условие в запросе. Чтобы удалить строки с условием Цена < 1000, мы отбираем строки с противоположным условием — Цена >= 1000. Таким образом, все неподходящие строки просто отсекаются за один проход.

Почему стандартный поиск НайтиСтроки() не всегда помогает?

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

  1. Реквизит управляемой формы Объект.Таблица имеет тип ДанныеФормыКоллекция, у которого физически нет метода НайтиСтроки(). Чтобы им воспользоваться, сначала придется применить метод РеквизитФормыВЗначение.
  2. Метод НайтиСтроки() принимает в качестве параметра структуру отбора, которая поддерживает только строгое равенство. Вы можете легко найти строки, где Цена = 0, но вы не сможете стандартным образом найти строки, где Цена < 1000.

Резюме: какой метод выбрать?

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

← На главную