Как исправить ошибку «Значение не является значением объектного типа (Выбрать)» при печати документа в 1С?

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

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

Выясним причину возникновения ошибки

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


ВЫБРАТЬ
    Выхода_на_практику.Номер,
    Выхода_на_практику.Дата,
    Выхода_на_практику.Студенты.(
        НомерСтроки,
        ФИО_студента,
        База,
        Основа_обучения
    )
ИЗ
    Документ.Выхода_на_практику КАК Выхода_на_практику
ГДЕ
    Выхода_на_практику.Ссылка В(&Ссылка)
    И Выхода_на_практику.Студенты.База = Значение(Перечисление.База_классов.классов_9)
    И Выхода_на_практику.Студенты.Основа_обучения = Значение(Перечисление.Основа_обучения.бюджетник)

Здесь кроется главная концептуальная ошибка работы с механизмом запросов платформы 1С:

  1. Поведение условий по табличным частям в секции ГДЕ. Обращение к полям вложенной таблицы в секции ГДЕ транслируется на уровень СУБД как проверка наличия подходящих записей. Если в документе присутствуют студенты только на базе 11 классов или обучающиеся на коммерческой основе, условие отбора возвращает ложь для всего документа. В итоге запрос отсекает документ целиком, и результирующая выборка верхнего уровня оказывается абсолютно пустой.
  2. Отсутствие проверки позиционирования выборки. При вызове Выборка.Следующий() без анализа возвращаемого булева значения указатель не встает ни на какую запись (так как строк в результате нет). Следующая строка кода пытается обратиться к реквизиту Выборка.Студенты, который при неудачном позиционировании содержит Неопределено или Null. Попытка вызвать метод Выбрать() у такого значения и приводит к ошибке «Значение не является значением объектного типа (Выбрать)».
  3. Ошибки инициализации переменных при проверке условий. Если обернуть вызов в условие Если Выборка1.Следующий() Тогда ВыборкаСтуденты = Выборка1.Студенты.Выбрать(); КонецЕсли;, но оставить ниже проверку Если ВыборкаСтуденты.Количество() > 0, возникнет новая ошибка: «Значение не является значением объектного типа (Количество)». Переменная ВыборкаСтуденты при отрицательном результате условия останется неинициализированной (со значением Неопределено).

Рассмотрим подробнее три надежных способа устранения этой проблемы.

Способ 1. Проверка результата запроса на пустоту и контроль метода Следующий()

Если структура запроса с вложенной таблицей временно сохраняется, необходимо гарантировать безопасную проверку данных перед выводом в ТабличныйДокумент. Для этого используем метод Пустой() объекта РезультатЗапроса и обязательную проверку успешности перехода по записям.

Посмотрим на пример безопасного обхода:


РезультатЗапроса = Запрос.Выполнить();

Если Не РезультатЗапроса.Пустой() Тогда
    ВыборкаДокумент = РезультатЗапроса.Выбрать();
    
    Если ВыборкаДокумент.Следующий() Тогда
        // Заполняем и выводим шапку, если документ удовлетворяет условиям
        ОбластьШапка.Параметры.Заполнить(ВыборкаДокумент);
        ТабДок.Вывести(ОбластьШапка);
        
        ВыборкаСтуденты = ВыборкаДокумент.Студенты.Выбрать();
        Если ВыборкаСтуденты.Количество() > 0 Тогда
            ТабДок.Вывести(ОбластьСтудентыШапка);
            
            Пока ВыборкаСтуденты.Следующий() Цикл
                ОбластьСтуденты.Параметры.Заполнить(ВыборкаСтуденты);
                ТабДок.Вывести(ОбластьСтуденты);
            КонецЦикла;
        КонецЕсли;
    КонецЕсли;
Иначе
    // Если документ не подошел под условия, выводим только заголовок или информационное сообщение
    ТабДок.Вывести(ОбластьЗаголовок);
КонецЕсли;

Способ 2. Разделение данных через пакетный запрос (рекомендуемый стандарт)

Синтаксис выборки вложенных таблиц через скобки считается устаревшим и снижает производительность СУБД. Согласно стандартам разработки 1С, для печатных форм оптимально применять пакетные запросы через метод ВыполнитьПакет(). Это позволяет гарантированно получить шапку документа в первом запросе, а строки с нужными фильтрами — во втором.

Разберем этот вариант по шагам:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|   Выход.Ссылка,
|   Выход.Номер,
|   Выход.Дата,
|   Выход.Дата_начала_практики,
|   Выход.Дата_конца_практики,
|   Выход.Группа,
|   Выход.Специальность
|ИЗ
|   Документ.Выхода_на_практику КАК Выход
|ГДЕ
|   Выход.Ссылка = &Ссылка
|;
|
|ВЫБРАТЬ
|   Студенты.НомерСтроки,
|   Студенты.ФИО_студента,
|   Студенты.Место_прохождения_практики,
|   Студенты.Договор,
|   Студенты.База,
|   Студенты.Основа_обучения
|ИЗ
|   Документ.Выхода_на_практику.Студенты КАК Студенты
|ГДЕ
|   Студенты.Ссылка = &Ссылка
|   И Студенты.База = Значение(Перечисление.База_классов.классов_9)
|   И Студенты.Основа_обучения = Значение(Перечисление.Основа_обучения.бюджетник)";

Запрос.Параметры.Вставить("Ссылка", Ссылка);

МассивРезультатов = Запрос.ВыполнитьПакет();

ТабДок.Очистить();
ТабДок.Вывести(ОбластьЗаголовок);

// 1. Обрабатываем шапку документа
ВыборкаШапка = МассивРезультатов[0].Выбрать();
Если ВыборкаШапка.Следующий() Тогда
    ОбластьШапка.Параметры.Заполнить(ВыборкаШапка);
    ОбластьШапка.Параметры.Дата_Начала_Практики = Формат(ВыборкаШапка.Дата_начала_практики, "ДЛФ=D");
    ОбластьШапка.Параметры.Дата_Конца_Практики = Формат(ВыборкаШапка.Дата_конца_практики, "ДЛФ=D");
    ТабДок.Вывести(ОбластьШапка);
КонецЕсли;

// 2. Обрабатываем строки табличной части
ВыборкаСтроки = МассивРезультатов[1].Выбрать();
Если ВыборкаСтроки.Количество() > 0 Тогда
    ТабДок.Вывести(ОбластьСтудентыШапка);
    
    Пока ВыборкаСтроки.Следующий() Цикл
        ОбластьСтуденты.Параметры.Заполнить(ВыборкаСтроки);
        ТабДок.Вывести(ОбластьСтуденты);
    КонецЦикла;
КонецЕсли;

ТабДок.Вывести(ОбластьПодвал);

При таком подходе шапка документа будет выведена на печать в любом случае, даже если среди строк табличной части нет ни одного студента на базе 9 классов.

Способ 3. Использование ЛЕВОГО СОЕДИНЕНИЯ с фильтрацией в секции ПО

Если требуется получить все данные одним запросом, объединим таблицу шапки с реальной таблицей табличной части через конструкцию ЛЕВОЕ СОЕДИНЕНИЕ. При этом критически важно поместить условия отбора строк таблицы именно в секцию соединения ПО, а не в секцию ГДЕ (иначе соединение превратится во внутреннее, и документ снова пропадет из результата при отсутствии подходящих строк).

Посмотрим на пример запроса с итогами:


ВЫБРАТЬ
    ДокументШапка.Ссылка КАК Ссылка,
    ДокументШапка.Номер КАК Номер,
    ДокументШапка.Дата КАК Дата,
    ТабличнаяЧасть.НомерСтроки КАК НомерСтроки,
    ТабличнаяЧасть.ФИО_студента КАК ФИО_студента,
    ТабличнаяЧасть.Место_прохождения_практики КАК Место_прохождения_практики
ИЗ
    Документ.Выхода_на_практику КАК ДокументШапка
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.Выхода_на_практику.Студенты КАК ТабличнаяЧасть
        ПО (ТабличнаяЧасть.Ссылка = ДокументШапка.Ссылка)
        И (ТабличнаяЧасть.База = Значение(Перечисление.База_классов.классов_9))
        И (ТабличнаяЧасть.Основа_обучения = Значение(Перечисление.Основа_обучения.бюджетник))
ГДЕ
    ДокументШапка.Ссылка = &Ссылка
ИТОГИ ПО
    Ссылка

Обход результата с итогами выполняется по группировкам:


ВыборкаДокумент = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Если ВыборкаДокумент.Следующий() Тогда
    ОбластьШапка.Параметры.Заполнить(ВыборкаДокумент);
    ТабДок.Вывести(ОбластьШапка);
    
    // Выборка детальных записей по строкам
    ВыборкаДетали = ВыборкаДокумент.Выбрать();
    Пока ВыборкаДетали.Следующий() Цикл
        // Проверяем, что строка действительно существует, а не получена как NULL от левого соединения
        Если ЗначениеЗаполнено(ВыборкаДетали.НомерСтроки) Тогда
            ОбластьСтуденты.Параметры.Заполнить(ВыборкаДетали);
            ТабДок.Вывести(ОбластьСтуденты);
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

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

← На главную