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

Программист 1С v7.7 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При разработке обработок и загрузке данных (например, банковских выписок) часто возникает задача отфильтровать ТаблицуЗначений, удалив из нее лишние строки перед переносом в документ. Начинающие разработчики часто пытаются вызывать метод УдалитьСтроку() внутри стандартного цикла перебора ПолучитьСтроку(), что приводит к аварийному завершению работы кода с ошибкой «Номер за пределами значения!».

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

Почему возникает ошибка «Номер за пределами значения!»

Проанализируем классический ошибочный пример:


Пока ТЗ_1.ПолучитьСтроку() = 1 Цикл
    Если (((ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1) И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление) И (Лев(ТЗ_1.НомДок, 2) <> "ОК"))) Тогда
        ТЗ_1.УдалитьСтроку(ТЗ_1.ТекущаяСтрока()); 
    КонецЕсли;
КонецЦикла;

При таком подходе происходят два критических сбоя:

  1. Смещение индексов: Метод ПолучитьСтроку() при каждом шаге цикла сдвигает внутренний указатель на следующую позицию (+1). Когда мы вызываем УдалитьСтроку(), все последующие строки физически смещаются вверх на одну позицию. В результате строка, занявшая место удаленной, пропускается без проверки.
  2. Выход за границы массива: Общее количество строк в объекте ТаблицаЗначений уменьшается, но внутренний курсор продолжает двигаться вперед до исходного количества строк. В определенный момент платформа пытается позиционироваться на номер строки, которого в таблице уже физически не существует, что и порождает исключение «Номер за пределами значения!».

Кроме того, в платформе 1С 7.7 метод ТекущаяСтрока() предназначен преимущественно для визуальных элементов формы, а внутри программного цикла следует использовать методы позиционирования по номеру строки.

Вариант 1. Удаление строк с конца таблицы с помощью цикла Пока

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

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


Ном = ТЗ_1.КоличествоСтрок();
Пока Ном > 0 Цикл
    ТЗ_1.ПолучитьСтрокуПоНомеру(Ном);
    Если (ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1) 
        И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление) 
        И (Лев(ТЗ_1.НомДок, 2) <> "ОК") Тогда
        
        ТЗ_1.УдалитьСтроку(Ном);
    КонецЕсли;
    Ном = Ном - 1;
КонецЦикла;

ЗагрузитьТабличнуюЧасть(ТЗ_1);

Вариант 2. Обратный обход через математический расчет в цикле Для

Если требуется использовать цикл Для, мы можем обойти ограничение положительного шага, вычисляя реальный номер строки по формуле:


ВсегоСтрок = ТЗ_1.КоличествоСтрок();
Для К = 1 По ВсегоСтрок Цикл
    Ном = ВсегоСтрок + 1 - К;
    ТЗ_1.ПолучитьСтрокуПоНомеру(Ном);
    
    Если (ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1) 
        И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление) 
        И (Лев(ТЗ_1.НомДок, 2) <> "ОК") Тогда
        
        ТЗ_1.УдалитьСтроку(Ном);
    КонецЕсли;
КонецЦикла;

Вариант 3. Обход с начала со сдвигом счетчика только при отсутствии удаления

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


Ном = 1;
Пока Ном <= ТЗ_1.КоличествоСтрок() Цикл
    ТЗ_1.ПолучитьСтрокуПоНомеру(Ном);
    
    Если (ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1) 
        И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление) 
        И (Лев(ТЗ_1.НомДок, 2) <> "ОК") Тогда
        
        ТЗ_1.УдалитьСтроку(Ном);
    Иначе
        Ном = Ном + 1;
    КонецЕсли;
КонецЦикла;

Вариант 4. Формирование новой таблицы значений (оптимально для больших объемов)

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


ТЗ_Результат = СоздатьОбъект("ТаблицаЗначений");
ТЗ_1.Выгрузить(ТЗ_Результат);
ТЗ_Результат.Очистить(); // Сохраняем структуру колонок

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

ЗагрузитьТабличнуюЧасть(ТЗ_Результат);

Вариант 5. Прямое заполнение документа без промежуточных удалений

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


УдалитьСтроки(); // Очищаем табличную часть текущего документа

ТЗ_1.ВыбратьСтроки();
Пока ТЗ_1.ПолучитьСтроку() = 1 Цикл
    Если НЕ ((ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1) 
        И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление) 
        И (Лев(ТЗ_1.НомДок, 2) <> "ОК")) Тогда
        
        НоваяСтрока();
        ПервичныйДокумент = ТЗ_1.ПервичныйДокумент;
        ВидДвижения       = ТЗ_1.ВидДвижения;
        НомДок            = ТЗ_1.НомДок;
        // Заполнение реквизитов табличной части документа
    КонецЕсли;
КонецЦикла;

Такой подход исключает ошибки курсора и работает максимально быстро и прозрачно.

← На главную