Как вывести несколько таблиц с отборами в печатную форму документа 1С и исправить ошибку выборки?

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

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

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

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

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


Запрос2.Параметры.Вставить("Ссылка", Ссылка);
Выборка1 = Запрос2.Выполнить().Выбрать(); 
ОбластьШапка1.Параметры.Заполнить(Выборка1);
// ...
ВыборкаСтуденты2 = Выборка1.Студенты.Выбрать();

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

Попытка вызвать метод Выбрать() у свойства Выборка1.Студенты приводит к исключению времени выполнения, поскольку обращение происходит к значению, не являющемуся объектом. Чтобы спозиционироваться на первой строке выборки верхнего уровня, обязательно требуется предварительно вызвать метод Следующий().

Исправленный вариант локального фрагмента кода выглядит следующим образом:


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

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

    ВыборкаСтуденты2 = Выборка1.Студенты.Выбрать();
    Пока ВыборкаСтуденты2.Следующий() Цикл
        ОбластьСтуденты1.Параметры.Заполнить(ВыборкаСтуденты2);
        ТабДок.Вывести(ОбластьСтуденты1, ВыборкаСтуденты2.Уровень());
    КонецЦикла;
КонецЕсли;

Подводные камни вложенных таблиц и запросов в цикле

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

  1. Особенность фильтрации вложенных таблиц: Если в запросе обращаться к шапке документа и использовать конструкцию Выхода_на_практику.Студенты.(...), то условие в секции ГДЕ лишь проверяет факт наличия хотя бы одной подходящей строки в документе. Если строка найдена, то во вложенную выборку попадут все строки табличной части без фильтрации.
  2. Выполнение запросов внутри цикла: Создание и вызов объекта Запрос внутри цикла обхода другой выборки приводит к избыточной нагрузке на СУБД (антипаттерн «запрос в цикле»).

Рассмотрим правильные и эффективные способы вывода нескольких таблиц в печатную форму.

Вариант 1. Использование одного запроса с итогами (Рекомендуемый)

Вместо четырех отдельных запросов обратимся напрямую к табличной части документа и сгруппируем данные с помощью секции ИТОГИ ПО. Это позволит за одно обращение к базе данных получить структурированные данные, разделенные по базе классов и основе обучения.


Процедура Печать(ТабДок, Ссылка) Экспорт

    Макет = Документы.Выхода_на_практику.ПолучитьМакет("Печать");
    ТабДок.Очистить();

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

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

    Если РезультатЗапроса.Пустой() Тогда
        Возврат;
    КонецЕсли;

    ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");
    ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
    ОбластьГруппировка = Макет.ПолучитьОбласть("СтудентыШапка");
    ОбластьСтрока = Макет.ПолучитьОбласть("Студенты");

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

        // Обход по первой группировке (База: 9 / 11 классов)
        ВыборкаБаза = ВыборкаДокумент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
        Пока ВыборкаБаза.Следующий() Цикл
            
            // Обход по второй группировке (Основа обучения: бюджет / платно)
            ВыборкаОснова = ВыборкаБаза.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
            Пока ВыборкаОснова.Следующий() Цикл
                
                // Выводим заголовок подтаблицы
                ОбластьГруппировка.Параметры.База = ВыборкаБаза.База;
                ОбластьГруппировка.Параметры.Основа_обучения = ВыборкаОснова.Основа_обучения;
                ТабДок.Вывести(ОбластьГруппировка);

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

            КонецЦикла;
        КонецЦикла;

    КонецЦикла;

КонецПроцедуры

Вариант 2. Пакетный запрос

Если для каждой из четырех таблиц требуется уникальное оформление макета и разный состав колонок, объединим все 4 запроса в единый пакетный запрос. Это избавит от необходимости отправлять запросы в цикле и сохранит производительность системы.


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

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

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

// Обработка второй таблицы
Выборка2 = МассивРезультатов[1].Выбрать();
Пока Выборка2.Следующий() Цикл
    // Вывод строк таблицы 2
КонецЦикла;

Вариант 3. Фильтрация строк через массив в оперативной памяти

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


ТЧ_Студенты = Ссылка.Студенты.Выгрузить();

Отбор = Новый Структура;
Отбор.Вставить("База", Перечисления.База_классов.классов_9);
Отбор.Вставить("Основа_обучения", Перечисления.Основа_обучения.бюджетник);

СтрокиБюджет9 = ТЧ_Студенты.НайтиСтроки(Отбор);
Для Каждого СтрокаТЧ Из СтрокиБюджет9 Цикл
    ОбластьСтуденты.Параметры.Заполнить(СтрокаТЧ);
    ТабДок.Вывести(ОбластьСтуденты);
КонецЦикла;

Таким образом, для исправления ошибки всегда проверяйте факт позиционирования выборки через метод Следующий(), а для разделения данных в отчетах используйте один запрос с конструкцией ИТОГИ ПО или пакетное выполнение.

← На главную