При формировании сложных печатных документов часто возникает задача: документ содержит несколько логических секций (например, распределение студентов по категориям, список разных типов номенклатуры или услуг), и если по какому-то из условий строки отсутствуют, соответствующая шапка таблицы не должна выводиться в результирующий табличный документ. Рассмотрим причины возникновения этой проблемы и разберем несколько способов её решения.
Проанализируем исходную ситуацию. В типовом коде печатной формы вывод параметров шапки и самих областей шапки часто помещают до входа в цикл обработки строк:
Выборка1 = Запрос2.Выполнить().Выбрать();
ОбластьШапка1.Параметры.Заполнить(Выборка1);
ТабДок.Вывести(ОбластьШапка1);
ТабДок.Вывести(ОбластьСтудентыШапка1);
Выборка1.Следующий();
ВыборкаСтуденты2 = Выборка1.Студенты.Выбрать();
Пока ВыборкаСтуденты2.Следующий() Цикл
ОбластьСтуденты.Параметры.Заполнить(ВыборкаСтуденты2);
ТабДок.Вывести(ОбластьСтуденты);
КонецЦикла;
В приведенном фрагменте присутствуют сразу две критические ошибки:
Следующий(): метод Выбрать() возвращает выборку, которая изначально установлена перед первой записью. Обращение к полям или вызов Заполнить(Выборка1) до первого вызова Следующий() заполнит параметры значениями Неопределено или вызовет исключение.ТабДок.Вывести() выполняются до того, как система проверяет наличие строк во вложенной выборке. В результате, даже если студентов по заданному условию нет, пустой заголовок таблицы попадает в документ.Разберем основные варианты правильной реализации вывода.
Это самый простой и быстрый способ в реализации. Мы инициализируем булеву переменную-флаг перед началом обхода строк. Сами шапки выводятся только внутри цикла при обработке первой найденной записи, после чего флаг сбрасывается. Если выборка окажется пустой, цикл не выполнится ни разу, и шапка не попадет в отчет.
Посмотрим на пример реализации:
Выборка1 = Запрос2.Выполнить().Выбрать();
Если Выборка1.Следующий() Тогда
// Заполняем реквизиты шапки документа
ОбластьШапка1.Параметры.Заполнить(Выборка1);
ОбластьШапка1.Параметры.Дата_Начала_Практики = Формат(Выборка1.Дата_начала_практики, "ДЛФ=D");
ОбластьШапка1.Параметры.Дата_Конца_Практики = Формат(Выборка1.Дата_конца_практики, "ДЛФ=D");
ВыборкаСтуденты = Выборка1.Студенты.Выбрать();
НужноВывестиШапку = Истина;
Пока ВыборкаСтуденты.Следующий() Цикл
// Выводим шапку только перед первой строкой таблицы
Если НужноВывестиШапку Тогда
ТабДок.Вывести(ОбластьШапка1);
ТабДок.Вывести(ОбластьСтудентыШапка1);
НужноВывестиШапку = Ложь;
КонецЕсли;
// Выводим строку с данными
ОбластьСтуденты.Параметры.Заполнить(ВыборкаСтуденты);
ТабДок.Вывести(ОбластьСтуденты);
КонецЦикла;
КонецЕсли;
Выясним, как избежать использования флагов. Для этого проверим наличие строк до вывода макета. В платформе 1С вложенная таблица Выборка1.Студенты возвращает объект РезультатЗапроса, у которого доступны методы Пустой() и Количество(), либо можно выполнить проверку по количеству записей выборки.
Разберем по шагам данный подход:
Выборка1 = Запрос2.Выполнить().Выбрать();
Если Выборка1.Следующий() Тогда
// Проверяем, есть ли строки во вложенной табличной части
Если НЕ Выборка1.Студенты.Пустой() Тогда
ОбластьШапка1.Параметры.Заполнить(Выборка1);
ТабДок.Вывести(ОбластьШапка1);
ТабДок.Вывести(ОбластьСтудентыШапка1);
ВыборкаСтуденты = Выборка1.Студенты.Выбрать();
Пока ВыборкаСтуденты.Следующий() Цикл
ОбластьСтуденты.Параметры.Заполнить(ВыборкаСтуденты);
ТабДок.Вывести(ОбластьСтуденты);
КонецЦикла;
КонецЕсли;
КонецЕсли;
Рассмотрим важную особенность языка запросов 1С: когда запрос строится к шапке документа с выборкой вложенной таблицы Документ.Студенты.(...), наложение условия в секции ГДЕ по реквизитам вложенной таблицы фильтрует только сам документ, но во вложенную выборку попадают все строки документа, а не только удовлетворяющие условию.
Кроме того, выполнение нескольких отдельных запросов к одному документу неэффективно с точки зрения производительности. Правильнее построить один запрос напрямую к табличной части с использованием секции ИТОГИ.
Посмотрим на пример запроса и обхода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Студенты.Ссылка КАК Документ,
| Студенты.Ссылка.Номер КАК Номер,
| Студенты.Ссылка.Дата КАК Дата,
| Студенты.Основа_обучения КАК Основа_обучения,
| Студенты.База КАК База,
| Студенты.ФИО_студента КАК ФИО_студента,
| Студенты.Место_прохождения_практики КАК Место_прохождения_практики
|ИЗ
| Документ.Выхода_на_практику.Студенты КАК Студенты
|ГДЕ
| Студенты.Ссылка = &Ссылка
|ИТОГИ ПО
| Основа_обучения,
| База";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
// Обходим группы: в результат попадут только существующие комбинации условий
ВыборкаГруппа = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГруппа.Следующий() Цикл
// Выводим шапку секции под конкретную категорию (Бюджет/Платник, 9/11 классов)
ОбластьСтудентыШапка.Параметры.Заполнить(ВыборкаГруппа);
ТабДок.Вывести(ОбластьСтудентыШапка);
// Обходим детальные записи внутри группы
ВыборкаДетали = ВыборкаГруппа.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
ОбластьСтуденты.Параметры.Заполнить(ВыборкаДетали);
ТабДок.Вывести(ОбластьСтуденты);
КонецЦикла;
КонецЦикла;
При таком подходе пустые секции отсекаются платформой автоматически на уровне группировок запроса, исключаются лишние обращения к СУБД, а код модуля печати становится компактным и легко поддерживаемым.