Как исправить ошибку «Значение индекса выходит за границы диапазона» при обходе табличной части документа в 1С?

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

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

Анализ проблемы: почему возникает исключение

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


Чек = Документы.ЧекККМ;
Ч = Чек.Выбрать(НачалоДня(ДатаЧеков), КонецДня(ДатаЧеков));
Пока Ч.Следующий() Цикл
    Для Каждого ТекущаяСтрока Из Ч.Товары Цикл
        Ном = ТекущаяСтрока.НомерСтроки;
        кодТ = Прав(Ч.Товары.Получить(Ном).Номенклатура.Код, 5);
        // ...

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

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

Решение 1. Прямое обращение к реквизитам через переменную итератора

Посмотрим на конструкцию цикла Для Каждого ... Из ... Цикл. В данном цикле переменная ТекущаяСтрока на каждой итерации уже содержит прямую ссылку на текущую обрабатываемую строку табличной части. Повторное обращение к коллекции через метод Получить() является абсолютно избыточным.

Упростим и исправим код, обратившись к номенклатуре напрямую через текущую строку:


Пока Ч.Следующий() Цикл
    Для Каждого ТекущаяСтрока Из Ч.Товары Цикл
        кодТ = Прав(ТекущаяСтрока.Номенклатура.Код, 5);
        // дальнейшая обработка
    КонецЦикла;
КонецЦикла;

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

Решение 2. Коррекция индекса при использовании метода «Получить»

Если по каким-либо причинам требуется обращаться к строке именно по ее номеру, необходимо учитывать смещение индекса на единицу:


Ном = ТекущаяСтрока.НомерСтроки;
кодТ = Прав(Ч.Товары.Получить(Ном - 1).Номенклатура.Код, 5);

Этот способ устраняет исключение, однако он считается неоптимальным стилем программирования в 1С при наличии итератора ТекущаяСтрока.

Ограничения выборки и переход на язык запросов

Рассмотрим важные архитектурные нюансы представленного алгоритма. Использование объекта ДокументВыборка (метод Выбрать()) и обращение через точку к табличной части Ч.Товары приводит к тому, что платформа неявно считывает каждый документ из базы данных целиком. Разыменование ссылок вроде ТекущаяСтрока.Номенклатура.Код в цикле порождает множество мелких запросов к СУБД, что критически замедляет работу системы при обработке большого объема документов за период.

Кроме того, выборка предназначена только для чтения. Если необходимо не просто получить код, но и записать новые значения в строку документа, потребуется получать объект документа через Ч.ПолучитьОбъект() и вызывать метод Записать().

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

  1. Обращаемся напрямую к реальной таблице Документ.ЧекККМ.Товары.
  2. Накладываем отбор по периоду даты документа в параметрах запроса.
  3. Используем левое соединение с регистром сведений (или виртуальной таблицей СрезПоследних для периодических регистров) по полю Номенклатура.
  4. Для выделения части кода можно использовать встроенную функцию ПОДСТРОКА прямо в тексте запроса или обработать результат функцией Прав() при обходе выборки результата запроса.

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЧекТовары.Ссылка КАК СсылкаНаДокумент,
    |	ЧекТовары.НомерСтроки КАК НомерСтроки,
    |	ЧекТовары.Номенклатура.Код КАК ПолныйКод,
    |	Цены.Цена КАК ЦенаИзРегистра
    |ИЗ
    |	Документ.ЧекККМ.Товары КАК ЧекТовары
    |	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены
    |		ПО ЧекТовары.Номенклатура = Цены.Номенклатура
    |ГДЕ
    |	ЧекТовары.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";

Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаЧеков));
Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаЧеков));
Запрос.УстановитьПараметр("Период", КонецДня(ДатаЧеков));

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    кодТ = Прав(Выборка.ПолныйКод, 5);
    // Обработка данных
КонецЦикла;

Применение языка запросов кардинально ускоряет обработку данных и предотвращает типовые ошибки работы с коллекциями объектов в оперативной памяти.

← На главную