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

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

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

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

Выясняем причину: почему простой цикл пропускает строки?

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


&НаСервере
Процедура УдалитьСтрокиНеправильно()
    Для Каждого СтрТч Из Объект.СравнениеОтчетов Цикл
        Если СтрТч.ВУчетаДавальца <> СтрТч.ВУчетеПереработчика Тогда
            Объект.СравнениеОтчетов.Удалить(СтрТч);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

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

  1. Строка №0 (Индекс 0) — Подлежит удалению
  2. Строка №1 (Индекс 1) — Подлежит удалению
  3. Строка №2 (Индекс 2) — Должна остаться
  4. Строка №3 (Индекс 3) — Подлежит удалению

Проанализируем по шагам работу платформы:

  1. Цикл начинается с индекса 0. Система проверяет Строку №0. Условие выполняется, строка удаляется.
  2. Внимание, сдвиг! После удаления Строки №0 все оставшиеся строки сдвигаются вверх. Теперь Строка №1 получает индекс 0, Строка №2 получает индекс 1, а Строка №3 — индекс 2.
  3. Цикл переходит к следующей итерации и обращается к индексу 1. Но под индексом 1 теперь находится Строка №2 (которая должна остаться). А бывшая Строка №1 (которая теперь имеет индекс 0) оказывается полностью пропущенной! Она никогда не проверится внутри этого цикла.

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

Давайте разберем правильные методы обхода этой проблемы, которые гарантируют корректный результат за один проход.

Способ 1. Использование вспомогательного массива (Решение с форума)

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

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


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

Преимущества метода: Код легко читается, логика разделена на два понятных этапа. Мы гарантированно избегаем проблем со сдвигом индексов.

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

Способ 2. Обратный цикл (Обход с конца таблицы)

Это классический профессиональный прием, который не требует создания дополнительных объектов в памяти (массивов или таблиц). Идея заключается в том, что мы начинаем обходить коллекцию не с первой строки, а с самой последней, двигаясь к началу (к индексу 0).

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

Посмотрим, как выглядит программная реализация обратного цикла:


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

Также данный алгоритм можно элегантно написать с использованием цикла Пока, что уменьшает вероятность запутаться в отрицательных индексах стандартного цикла Для:


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

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

Недостатки: Требует внимательности при написании структуры цикла, чтобы не допустить бесконечного зацикливания или выхода за границы массива.

Способ 3. Перезаполнение таблицы (Для высоконагруженных систем)

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

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

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


&НаСервере
Процедура ОптимизированноеПерезаполнениеТаблицы()
    // Шаг 1: Выгружаем структуру колонок нашей табличной части в новую Таблицу Значений
    ТаблицаОставшихсяДанных = Объект.СравнениеОтчетов.ВыгрузитьКолонки();
    
    // Шаг 2: Заполняем новую таблицу только теми строками, которые НЕ подлежат удалению
    Для Каждого СтрТч Из Объект.СравнениеОтчетов Цикл
        
        // Инвертируем условие удаления. Нам нужно оставить строки, где:
        // Значения равны И при этом они НЕ равны нулю.
        Если (СтрТч.ВУчетаДавальца = СтрТч.ВУчетеПереработчика) 
            И (СтрТч.ВУчетаДавальца <> 0) Тогда
            
            // Копируем строку в новую таблицу
            НоваяСтрока = ТаблицаОставшихсяДанных.Добавить();
            ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрТч);
        КонецЕсли;
    КонецЦикла;
    
    // Шаг 3: Полностью очищаем старую табличную часть и загружаем отфильтрованные данные
    Объект.СравнениеОтчетов.Загрузить(ТаблицаОставшихсяДанных);
КонецПроцедуры

Преимущества метода: Невероятная скорость работы на больших объемах данных (от 10 000 строк и выше). Платформа производит запись в память один раз при вызове метода Загрузить().

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

Важные нюансы при работе на сервере и клиенте

При реализации удаления строк всегда обращайте внимание на контекст выполнения кода:

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

← На главную