При разработке обработок и загрузке данных (например, банковских выписок) часто возникает задача отфильтровать ТаблицуЗначений, удалив из нее лишние строки перед переносом в документ. Начинающие разработчики часто пытаются вызывать метод УдалитьСтроку() внутри стандартного цикла перебора ПолучитьСтроку(), что приводит к аварийному завершению работы кода с ошибкой «Номер за пределами значения!».
Разберем природу этой ошибки, выясним причину сбоя внутреннего курсора платформы 1С 7.7 и рассмотрим надежные варианты решения этой задачи.
Проанализируем классический ошибочный пример:
Пока ТЗ_1.ПолучитьСтроку() = 1 Цикл
Если (((ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1) И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление) И (Лев(ТЗ_1.НомДок, 2) <> "ОК"))) Тогда
ТЗ_1.УдалитьСтроку(ТЗ_1.ТекущаяСтрока());
КонецЕсли;
КонецЦикла;
При таком подходе происходят два критических сбоя:
ПолучитьСтроку() при каждом шаге цикла сдвигает внутренний указатель на следующую позицию (+1). Когда мы вызываем УдалитьСтроку(), все последующие строки физически смещаются вверх на одну позицию. В результате строка, занявшая место удаленной, пропускается без проверки.ТаблицаЗначений уменьшается, но внутренний курсор продолжает двигаться вперед до исходного количества строк. В определенный момент платформа пытается позиционироваться на номер строки, которого в таблице уже физически не существует, что и порождает исключение «Номер за пределами значения!».Кроме того, в платформе 1С 7.7 метод ТекущаяСтрока() предназначен преимущественно для визуальных элементов формы, а внутри программного цикла следует использовать методы позиционирования по номеру строки.
Самый надежный и прозрачный способ модификации таблицы «на месте» — обход с последней строки к первой. При удалении элемента с конца таблицы индексы всех вышестоящих (предыдущих) строк не меняются, поэтому пропуск данных исключен.
В синтаксисе 1С 7.7 цикл Для работает строго с шагом +1 (отрицательный шаг не поддерживается), поэтому обратный проход удобнее всего организовывать через цикл Пока:
Ном = ТЗ_1.КоличествоСтрок();
Пока Ном > 0 Цикл
ТЗ_1.ПолучитьСтрокуПоНомеру(Ном);
Если (ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1)
И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление)
И (Лев(ТЗ_1.НомДок, 2) <> "ОК") Тогда
ТЗ_1.УдалитьСтроку(Ном);
КонецЕсли;
Ном = Ном - 1;
КонецЦикла;
ЗагрузитьТабличнуюЧасть(ТЗ_1);
Если требуется использовать цикл Для, мы можем обойти ограничение положительного шага, вычисляя реальный номер строки по формуле:
ВсегоСтрок = ТЗ_1.КоличествоСтрок();
Для К = 1 По ВсегоСтрок Цикл
Ном = ВсегоСтрок + 1 - К;
ТЗ_1.ПолучитьСтрокуПоНомеру(Ном);
Если (ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1)
И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление)
И (Лев(ТЗ_1.НомДок, 2) <> "ОК") Тогда
ТЗ_1.УдалитьСтроку(Ном);
КонецЕсли;
КонецЦикла;
Мы можем обойти таблицу и от первой строки к последней, если будем увеличивать номер текущей строки только тогда, когда удаление не производилось. Если строка была удалена, следующая за ней автоматически встает на ее номер, поэтому увеличивать счетчик не нужно:
Ном = 1;
Пока Ном <= ТЗ_1.КоличествоСтрок() Цикл
ТЗ_1.ПолучитьСтрокуПоНомеру(Ном);
Если (ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1)
И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление)
И (Лев(ТЗ_1.НомДок, 2) <> "ОК") Тогда
ТЗ_1.УдалитьСтроку(Ном);
Иначе
Ном = Ном + 1;
КонецЕсли;
КонецЦикла;
Операция УдалитьСтроку() выполняет физический сдвиг памяти. При обработке больших массивов (тысячи строк) многократные удаления вызывают квадратичную сложность алгоритма и замедляют работу программы. В таких случаях гораздо быстрее создать новую таблицу с аналогичной структурой колонок и скопировать туда только нужные строки:
ТЗ_Результат = СоздатьОбъект("ТаблицаЗначений");
ТЗ_1.Выгрузить(ТЗ_Результат);
ТЗ_Результат.Очистить(); // Сохраняем структуру колонок
ТЗ_1.ВыбратьСтроки();
Пока ТЗ_1.ПолучитьСтроку() = 1 Цикл
// Инвертируем условие: отбираем только подходящие данные
Если НЕ ((ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1)
И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление)
И (Лев(ТЗ_1.НомДок, 2) <> "ОК")) Тогда
ТЗ_Результат.НоваяСтрока();
// Заполняем колонки результирующей таблицы
ТЗ_Результат.ПервичныйДокумент = ТЗ_1.ПервичныйДокумент;
ТЗ_Результат.ВидДвижения = ТЗ_1.ВидДвижения;
ТЗ_Результат.НомДок = ТЗ_1.НомДок;
// Заполнение остальных колонок...
КонецЕсли;
КонецЦикла;
ЗагрузитьТабличнуюЧасть(ТЗ_Результат);
Если конечная цель — перенос данных в документ, промежуточная очистка исходной таблицы значений вовсе не требуется. Достаточно предварительно очистить строки самого документа методом УдалитьСтроки(), а затем в цикле добавлять только те строки, которые соответствуют условиям:
УдалитьСтроки(); // Очищаем табличную часть текущего документа
ТЗ_1.ВыбратьСтроки();
Пока ТЗ_1.ПолучитьСтроку() = 1 Цикл
Если НЕ ((ПустаяСтрока(ТЗ_1.ПервичныйДокумент) = 1)
И (ТЗ_1.ВидДвижения <> Перечисление.ВидыДвиженийПоРасчетномуСчету.Поступление)
И (Лев(ТЗ_1.НомДок, 2) <> "ОК")) Тогда
НоваяСтрока();
ПервичныйДокумент = ТЗ_1.ПервичныйДокумент;
ВидДвижения = ТЗ_1.ВидДвижения;
НомДок = ТЗ_1.НомДок;
// Заполнение реквизитов табличной части документа
КонецЕсли;
КонецЦикла;
Такой подход исключает ошибки курсора и работает максимально быстро и прозрачно.