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

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

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

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

Почему классическое удаление строк в цикле "портит" индексы и тормозит?

Прежде чем перейти к готовым алгоритмам, давайте разберем физику процесса. Когда мы вызываем метод Удалить() для строки табличной части в цикле, платформа выполняет физическое перестроение индексов коллекции в оперативной памяти. Если исходная таблица содержит десятки тысяч строк, а удалению подлежит значительная их часть, операция сдвига элементов и перестроения структуры данных выполняется на каждой итерации цикла. Это превращает алгоритм в крайне неэффективный с вычислительной сложностью O(N * M), где N — общее число строк, а M — количество удаляемых строк.

Кроме того, при обычном обходе коллекции "сверху вниз" (от нулевого индекса к последнему) удаление текущей строки приводит к смещению индексов всех последующих строк. В результате следующий шаг цикла "перепрыгивает" через одну строку, что ведет к логическим ошибкам и неполному удалению данных.

Давайте рассмотрим правильные, оптимизированные сценарии решения обеих задач.

---

Задача №1: Оставить только строки с совпадающим значением текущей строки

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

Разберем по шагам самый быстрый и изящный алгоритм:

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

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


// Получаем текущую строку табличной части
ТекСтрока = ЭлементыФормы.Товары.ТекущаяСтрока;
Если ТекСтрока = Неопределено Тогда
    Возврат; // Строка не выбрана
КонецЕсли;

// Шаг 1: Выгружаем табличную часть в Таблицу Значений
ТабЗначений = Товары.Выгрузить();

// Шаг 2: Создаем структуру отбора по нужному значению колонки
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Номенклатура", ТекСтрока.Номенклатура);

// Шаг 3: Копируем только подходящие строки штатными средствами платформы
ОставшиесяСтроки = ТабЗначений.Скопировать(ПараметрыОтбора);

// Шаг 4: Очищаем исходную ТЧ и загружаем отфильтрованные данные обратно
Товары.Загрузить(ОставшиесяСтроки);

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

---

Задача №2: Удалить строки с совпадающим значением (кроме текущей)

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

Вариант А. Метод "Флаг и Копирование" (Идеально для больших таблиц)

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

Рассмотрим этот алгоритм подробнее:

  1. Выгружаем табличную часть в Таблицу Значений.
  2. Добавляем в нее временную колонку типа Булево с именем "Удалить".
  3. Используем быстрый метод НайтиСтроки(), чтобы получить массив всех дублирующих строк.
  4. В быстром цикле проставляем маркер Истина в колонку "Удалить" для найденных строк (пропуская саму текущую строку).
  5. Копируем только те строки, у которых маркер равен Ложь.
  6. Удаляем временную колонку и загружаем результат обратно в табличную часть.

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


ТекСтрока = ЭлементыФормы.Товары.ТекущаяСтрока;
Если ТекСтрока = Неопределено Тогда
    Возврат;
КонецЕсли;

// Шаг 1: Выгрузка данных в память
ТЗ = Товары.Выгрузить();

// Шаг 2: Добавляем временную колонку-маркер
ТЗ.Колонки.Добавить("Удалить", Новый ОписаниеТипов("Булево"));

// Шаг 3: Быстрый поиск дублирующих строк
ПараметрыПоиска = Новый Структура("Номенклатура", ТекСтрока.Номенклатура);
НайденныеСтроки = ТЗ.НайтиСтроки(ПараметрыПоиска);

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

// Шаг 5-6: Получаем чистую таблицу без удаляемых строк
РезультатТЗ = ТЗ.Скопировать(Новый Структура("Удалить", Ложь));
РезультатТЗ.Колонки.Удалить("Удалить");

// Загружаем оптимизированные данные обратно
Товары.Загрузить(РезультатТЗ);

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

Вариант Б. Классический "Обратный цикл" (Для небольших и средних таблиц)

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

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

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


ТекСтрока = ЭлементыФормы.Товары.ТекущаяСтрока;
Если ТекСтрока = Неопределено Тогда
    Возврат;
КонецЕсли;

ИндексТекущей = Товары.Индекс(ТекСтрока);
ЗначениеКолонки = ТекСтрока.Номенклатура;

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

---

Почему использование Запроса для локальной фильтрации табличных частей — это плохая практика?

В процессе обсуждения на форумах часто всплывает идея использовать механизм запросов 1С для фильтрации или удаления дублей (например, через конструкцию ПОМЕСТИТЬ ВременнаяТаблица ИЗ &Таблица). Начинающим разработчикам кажется, что SQL-сервер сделает эту работу быстрее. Это глубокое заблуждение.

Проанализируем детально, что происходит "под капотом" платформы при передаче локальной таблицы в запрос:

  1. Сериализация данных: Платформа 1С вынуждена перевести всю вашу таблицу значений из оперативной памяти сервера приложений в бинарный или XML-формат для передачи на СУБД (MS SQL, PostgreSQL и др.).
  2. Транспортные расходы: Данные передаются по сети между сервером приложений 1С и сервером СУБД.
  3. Нагрузка на СУБД: На стороне СУБД создается реальная физическая временная таблица (в базе TempDB), в нее записываются переданные данные, строится индекс. Затем СУБД выполняет SQL-запрос, формирует выборку, после чего временная таблица уничтожается.
  4. Десериализация: Результат запроса передается обратно на сервер приложений 1С, где платформа парсит полученный пакет данных и заново собирает объект ТаблицаЗначений.

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

---

Полезные советы по ускорению работы с таблицами

Для того чтобы ваши алгоритмы летали, возьмите на вооружение два простых правила:

  1. Индексируйте колонки в таблице значений. Если вы выгрузили табличную часть в ТаблицуЗначений и планируете осуществлять поиск строк методом НайтиСтроки(), предварительно проиндексируйте колонку поиска. Это существенно сократит время поиска на больших массивах данных: ТабЗначений.Индексы.Добавить("Номенклатура");
  2. Отключайте обновление интерфейса. Если ваш код выполняется в контексте клиентской формы (особенно в обычных формах 1С), то удаление или изменение каждой строки заставляет интерфейс принудительно перерисовывать таблицу на экране. Чтобы избежать мерцания экрана и ускорить процесс, производите все тяжелые расчеты на сервере, либо временно отключайте обновление формы на время выполнения транзакции.
← На главную