Почему возникает ошибка «Индекс находится за границами массива» при заполнении массива из запроса в 1С?

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

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

Анализ исходного кода и проявление проблемы

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


&НаСервере
Функция ЗапросКонтейнеров(Ссылка)
    МассивКонтейнеры = Новый Массив(6, 6);
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ПеремещениеТоваровПломбированиеТО.ТипОборудования,
        |   ПеремещениеТоваровПломбированиеТО.НомерТО,
        |   ПеремещениеТоваровПломбированиеТО.Замок,
        |   ПеремещениеТоваровПломбированиеТО.Пломба1,
        |   ПеремещениеТоваровПломбированиеТО.Пломба2
        |ИЗ
        |   Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПеремещениеТоваровПломбированиеТО
        |   ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
        |       ПО ПеремещениеТоваровПломбированиеТО.Ссылка = ПеремещениеТоваровТовары.Ссылка
        |ГДЕ
        |   ПеремещениеТоваровТовары.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    РезультатЗапроса = Запрос.Выполнить();
    ВыборкаКонтейнеры = РезультатЗапроса.Выбрать();
    
    Счетчик = 0;
    Пока ВыборкаКонтейнеры.Следующий() Цикл
        МассивКонтейнеры[Счетчик][0] = ВыборкаКонтейнеры.ТипОборудования;
        МассивКонтейнеры[Счетчик][1] = ВыборкаКонтейнеры.НомерТО;
        МассивКонтейнеры[Счетчик][2] = ВыборкаКонтейнеры.Замок;
        МассивКонтейнеры[Счетчик][3] = ВыборкаКонтейнеры.Пломба1;
        МассивКонтейнеры[Счетчик][4] = ВыборкаКонтейнеры.Пломба2;
        Счетчик = Счетчик + 1;
    КонецЦикла;
    
    Возврат МассивКонтейнеры;
КонецФункции

При выполнении этого кода на строке присвоения первого поля платформа генерирует ошибку индексации. Разберем по шагам, почему это происходит.

Причина 1: Некорректное соединение табличных частей в запросе

Главная логическая ошибка кроется в тексте запроса. Проанализируем конструкцию соединения:


ИЗ
    Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПеремещениеТоваровПломбированиеТО
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
        ПО ПеремещениеТоваровПломбированиеТО.Ссылка = ПеремещениеТоваровТовары.Ссылка

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

Если число товаров превышает 6, значение переменной Счетчик на 7-й итерации цикла достигнет 6. Так как массив был создан с фиксированным размером 6 (индексы от 0 до 5), попытка обращения к МассивКонтейнеры[6] немедленно вызывает ошибку выхода за границы.

Причина 2: Специфика многомерных массивов фиксированной длины в 1С

Конструкция Новый Массив(6, 6) создает матрицу элементов с фиксированным числом строк и колонок (от 0 до 5 по каждому измерению). В платформе 1С массив не расширяется автоматически при обращении по несуществующему индексу через квадратные скобки. Чтобы динамически добавлять элементы в коллекцию, необходимо использовать одномерный массив и метод Добавить().

Способ 1: Исправление текста запроса

Если нам требуются данные исключительно из табличной части ПломбированиеТО, соединять ее с другими табличными частями или шапкой документа не требуется. Фильтрация по конкретному документу выполняется напрямую по полю Ссылка табличной части:


Запрос.Текст = 
    "ВЫБРАТЬ
    |   ПломбированиеТО.ТипОборудования,
    |   ПломбированиеТО.НомерТО,
    |   ПломбированиеТО.Замок,
    |   ПломбированиеТО.Пломба1,
    |   ПломбированиеТО.Пломба2
    |ИЗ
    |   Документ.ПеремещениеТоваров.ПломбированиеТО КАК ПломбированиеТО
    |ГДЕ
    |   ПломбированиеТО.Ссылка = &Ссылка";

Убрав лишнее соединение, мы исключаем дублирование строк и получаем в выборке ровно столько записей, сколько фактически введено в документе.

Способ 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

Способ 3: Потоковая выгрузка в XML без промежуточных коллекций

Если данные из запроса нужны только для сериализации в 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

Рекомендации по форматированию данных и отладке

  1. Использование XMLСтрока(): Для преобразования значений в строковый формат XML рекомендуется использовать встроенную функцию XMLСтрока(Значение) вместо Формат(Значение, "ЧГ="). Она автоматически преобразует типы по стандартам XSD (убирает разделители групп разрядов в числах, корректно представляет даты и булевы флаги).
  2. Проверка верхней границы: Если алгоритм требует явного обращения по индексам, верхнюю границу массива всегда следует контролировать методом Массив.ВГраница(), который возвращает Массив.Количество() - 1.
  3. Отладка исключений: Для быстрого поиска причин переполнения коллекций в конфигураторе используйте меню Отладка — Остановка по ошибке. Это позволит остановить выполнение в точке сбоя и моментально увидеть текущее значение счетчиков и размер коллекций.
← На главную