При разработке печатных форм в 1С часто возникает классическая задача: необходимо вывести данные документа так, чтобы сначала отображалась шапка (дата, номер, контрагент), затем заголовок группы номенклатуры (папка/родитель), а под ним — перечень товаров, входящих в эту группу. Для этого мы обычно используем итоги в запросе и обход результата по группировкам. Однако многие разработчики сталкиваются с тем, что на верхних уровнях группировки вместо нужных данных возвращаются значения NULL. Давайте разберем эту ситуацию по шагам и выясним причину такого поведения платформы.
Когда мы выполняем запрос к базе данных и указываем секцию ИТОГИ ПО ПредметЗакупкиРодитель, платформа формирует иерархическую выборку. Если мы получаем данные через РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), система возвращает нам записи уровня группировки. Проблема заключается в том, что платформа 1С не знает, какое именно значение из множества детальных записей (например, количество, сумму или дату документа) поместить в итоговую строку группы. Поскольку явных указаний нет, система просто возвращает NULL.
Чтобы система понимала, как рассчитывать значения полей при формировании иерархии итогов, мы должны дать ей четкие инструкции с помощью агрегатных функций.
Рассмотрим подробнее, как нужно изменить запрос. Чтобы получить данные для шапки (документ в целом), затем для родителя номенклатуры и, наконец, для детальных записей, нам потребуется трехуровневая группировка. Также применим функции для числовых полей и реквизитов шапки.
// ... текст выбора полей ...
ГДЕ
ЗаявкаНаЗакупку.Ссылка = &Документ
УПОРЯДОЧИТЬ ПО
Ссылка
ИТОГИ
СУММА(Количество),
СУММА(Сумма),
СУММА(ОстатокНаСкладе),
МАКСИМУМ(Дата),
МАКСИМУМ(Номер)
ПО
Ссылка,
ПредметЗакупкиРодитель,
ПредметЗакупки
В этом примере мы добавили уровень документа (Ссылка). Это позволит нам при первом обходе сразу получить реквизиты шапки. Использование агрегатных функций, таких как СУММА() и МАКСИМУМ(), заставит платформу поднять эти значения на уровень группировок, и вместо NULL мы получим реальные цифры.
Теперь посмотрим на пример кода, который правильно обходит получившуюся трехуровневую выборку и заполняет макет табличного документа. Разберем по шагам логику вложенных циклов.
// 1. Уровень шапки документа
ВыборкаДокумент = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокумент.Следующий() Цикл
// Заполняем параметры макета для шапки (Дата, Номер без NULL)
ОбластьШапка.Параметры.Заполнить(ВыборкаДокумент);
ТабДок.Вывести(ОбластьШапка);
// 2. Уровень родителя (группы номенклатуры)
ВыборкаРодитель = ВыборкаДокумент.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаРодитель.Следующий() Цикл
// Выводим строку с названием группы (папки)
ОбластьГруппа.Параметры.ПредметЗакупкиРодитель = ВыборкаРодитель.ПредметЗакупкиРодитель;
ТабДок.Вывести(ОбластьГруппа);
// 3. Уровень детальных записей (сама номенклатура)
// Обратите внимание, здесь обход по умолчанию (прямой)
ВыборкаДетали = ВыборкаРодитель.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
// Выводим строки с конкретным товаром, количеством и суммой
ОбластьСтрока.Параметры.Заполнить(ВыборкаДетали);
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
КонецЦикла;
КонецЦикла;
Важно помнить, что каждый последующий метод Выбрать() вызывается от переменной предыдущей выборки, что обеспечивает спуск вглубь дерева результатов.
Альтернативный вариант — использовать в запросе итоги по иерархии: ИТОГИ ПО ПредметЗакупки ИЕРАРХИЯ. Однако при таком подходе есть важный нюанс. Чтобы правильно спуститься внутрь иерархии при обходе, в методе Выбрать обязательно нужно указывать имя группировки вторым параметром.
Если этого не сделать, цикл может вернуть только группы самого верхнего уровня и не пойти вглубь. Правильный вызов выглядит так:
ВыборкаИерархия = Выборка.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией, "ПредметЗакупки");
Для того чтобы в печатной форме (в режиме предприятия) пользователь мог сворачивать и разворачивать группы номенклатуры "плюсиками" слева (как это реализовано в типовых отчетах), мы можем использовать функционал автогруппировки.
Перед началом вывода первого цикла (уровня родителя) включим автогруппировку строк:
ТабДок.НачатьАвтогруппировкуСтрок();
А после завершения всех вложенных циклов обязательно закроем её:
ТабДок.ЗакончитьАвтогруппировкуСтрок();
Сами области макета при этом просто выводятся методом ТабДок.Вывести(Область), а платформа автоматически расставит нужные уровни группировки.
Рассмотрим современную практику программирования на платформе 1С. Для сложных многоуровневых печатных форм всё чаще отказываются от ручного вывода макетов с множеством вложенных циклов обхода. Вместо этого используется Система Компоновки Данных (СКД).
NULL).ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент.Этот подход требует значительно меньше программного кода и полностью избавляет от типичных ошибок с обходами выборки и пустыми значениями.