Исключение «Индекс находится за границами массива» — одна из самых частых ошибок при работе с коллекциями в платформе 1С:Предприятие. Она возникает в тот момент, когда программный код пытается обратиться к элементу массива по индексу, превышающему его реальный размер или верхнюю границу. Разберем типичную ситуацию, когда ошибка проявляется при обработке выборки запроса, выясним причины ее возникновения и рассмотрим правильные варианты решения задачи.
Рассмотрим типовой фрагмент кода, в котором разработчик пытается выбрать данные из табличной части документа, поместить их в двумерный массив фиксированной размерности и затем выгрузить в XML:
&НаСервере
Функция ЗапросКонтейнеров(Ссылка)
МассивКонтейнеры = Новый Массив(6, 6);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПеремещениеТоваровПломбированиеТО.ТипОборудования,
| ПеремещениеТоваровПломбированиеТО.НомерТО,
| ПеремещениеТоваровПломбированиеТО.Замок,
| ПеремещениеТоваровПломбированиеТО.Пломба1,
| ПеремещениеТоваровПломбированиеТО.Пломба2
|ИЗ
| Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПеремещениеТоваровПломбированиеТО
| ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
| ПО ПеремещениеТоваровПломбированиеТО.Ссылка = ПеремещениеТоваровТовары.Ссылка
|ГДЕ
| ПеремещениеТоваровТовары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаКонтейнеры = РезультатЗапроса.Выбрать();
Счетчик = 0;
Пока ВыборкаКонтейнеры.Следующий() Цикл
МассивКонтейнеры[Счетчик][0] = ВыборкаКонтейнеры.ТипОборудования;
МассивКонтейнеры[Счетчик][1] = ВыборкаКонтейнеры.НомерТО;
МассивКонтейнеры[Счетчик][2] = ВыборкаКонтейнеры.Замок;
МассивКонтейнеры[Счетчик][3] = ВыборкаКонтейнеры.Пломба1;
МассивКонтейнеры[Счетчик][4] = ВыборкаКонтейнеры.Пломба2;
Счетчик = Счетчик + 1;
КонецЦикла;
Возврат МассивКонтейнеры;
КонецФункции
При выполнении этого кода на строке присвоения первого поля платформа генерирует ошибку индексации. Разберем по шагам, почему это происходит.
Главная логическая ошибка кроется в тексте запроса. Проанализируем конструкцию соединения:
ИЗ
Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПеремещениеТоваровПломбированиеТО
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
ПО ПеремещениеТоваровПломбированиеТО.Ссылка = ПеремещениеТоваровТовары.Ссылка
Табличные части одного и того же документа связаны между собой только ссылкой на документ-владелец. Если в табличной части ПломбированиеТО содержится 1 строка, а в табличной части Товары этого же документа — 10 строк, то при соединении по полю Ссылка произойдет декартово произведение записей. В результате запрос вернет не 1 строку, а 10 дублирующихся строк (по количеству строк в товарах).
Если число товаров превышает 6, значение переменной Счетчик на 7-й итерации цикла достигнет 6. Так как массив был создан с фиксированным размером 6 (индексы от 0 до 5), попытка обращения к МассивКонтейнеры[6] немедленно вызывает ошибку выхода за границы.
Конструкция Новый Массив(6, 6) создает матрицу элементов с фиксированным числом строк и колонок (от 0 до 5 по каждому измерению). В платформе 1С массив не расширяется автоматически при обращении по несуществующему индексу через квадратные скобки. Чтобы динамически добавлять элементы в коллекцию, необходимо использовать одномерный массив и метод Добавить().
Если нам требуются данные исключительно из табличной части ПломбированиеТО, соединять ее с другими табличными частями или шапкой документа не требуется. Фильтрация по конкретному документу выполняется напрямую по полю Ссылка табличной части:
Запрос.Текст =
"ВЫБРАТЬ
| ПломбированиеТО.ТипОборудования,
| ПломбированиеТО.НомерТО,
| ПломбированиеТО.Замок,
| ПломбированиеТО.Пломба1,
| ПломбированиеТО.Пломба2
|ИЗ
| Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПломбированиеТО
|ГДЕ
| ПломбированиеТО.Ссылка = &Ссылка";
Убрав лишнее соединение, мы исключаем дублирование строк и получаем в выборке ровно столько записей, сколько фактически введено в документе.
Хранение разнородных данных в многомерных массивах с числовыми индексами колонок (0, 1, 2...) снижает читаемость кода и провоцирует ошибки. Стандарт разработки 1С рекомендует формировать динамический массив структур:
&НаСервере
Функция ЗапросКонтейнеров(Ссылка)
МассивКонтейнеры = Новый Массив;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПломбированиеТО.ТипОборудования,
| ПломбированиеТО.НомерТО,
| ПломбированиеТО.Замок,
| ПломбированиеТО.Пломба1,
| ПломбированиеТО.Пломба2
|ИЗ
| Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПломбированиеТО
|ГДЕ
| ПломбированиеТО.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаДанных = Новый Структура;
СтрокаДанных.Вставить("ТипОборудования", Выборка.ТипОборудования);
СтрокаДанных.Вставить("НомерТО", Выборка.НомерТО);
СтрокаДанных.Вставить("Замок", Выборка.Замок);
СтрокаДанных.Вставить("Пломба1", Выборка.Пломба1);
СтрокаДанных.Вставить("Пломба2", Выборка.Пломба2);
МассивКонтейнеры.Добавить(СтрокаДанных);
КонецЦикла;
Возврат МассивКонтейнеры;
КонецФункции
Обход такого массива при формировании XML выполняется безопасно через цикл Для Каждого, без использования ручных счетчиков и риска выйти за границы:
Массив = ЗапросКонтейнеров(Элемент.Значение);
ЗаписьXML.ЗаписатьНачалоЭлемента("Containers");
Для Каждого ЭлементМассива Из Массив Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("Container");
ЗаписьXML.ЗаписатьНачалоЭлемента("ContainerKind");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ЭлементМассива.ТипОборудования));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("ContainerNumber");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ЭлементМассива.НомерТО));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("NumberLock");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ЭлементМассива.Замок));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("NumberSeal");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ЭлементМассива.Пломба1));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("NumberSeal2");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(ЭлементМассива.Пломба2));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента(); // Container
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента(); // Containers
Если данные из запроса нужны только для сериализации в XML-файл, наиболее производительным решением будет прямая запись узлов в процессе обхода выборки запроса. Это экономит оперативную память сервера и исключает любые промежуточные коллекции:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПломбированиеТО.ТипОборудования,
| ПломбированиеТО.НомерТО,
| ПломбированиеТО.Замок,
| ПломбированиеТО.Пломба1,
| ПломбированиеТО.Пломба2
|ИЗ
| Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПломбированиеТО
|ГДЕ
| ПломбированиеТО.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Элемент.Значение);
Выборка = Запрос.Выполнить().Выбрать();
ЗаписьXML.ЗаписатьНачалоЭлемента("Containers");
Пока Выборка.Следующий() Цикл
ЗаписьXML.ЗаписатьНачалоЭлемента("Container");
ЗаписьXML.ЗаписатьНачалоЭлемента("ContainerKind");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(Выборка.ТипОборудования));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("ContainerNumber");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(Выборка.НомерТО));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("NumberLock");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(Выборка.Замок));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("NumberSeal");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(Выборка.Пломба1));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьНачалоЭлемента("NumberSeal2");
ЗаписьXML.ЗаписатьТекст(XMLСтрока(Выборка.Пломба2));
ЗаписьXML.ЗаписатьКонецЭлемента();
ЗаписьXML.ЗаписатьКонецЭлемента(); // Container
КонецЦикла;
ЗаписьXML.ЗаписатьКонецЭлемента(); // Containers
XMLСтрока(): Для преобразования значений в строковый формат XML рекомендуется использовать встроенную функцию XMLСтрока(Значение) вместо Формат(Значение, "ЧГ="). Она автоматически преобразует типы по стандартам XSD (убирает разделители групп разрядов в числах, корректно представляет даты и булевы флаги).Массив.ВГраница(), который возвращает Массив.Количество() - 1.