Как правильно составить запрос и вывести печатную форму с группировкой по номенклатурным группам?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Анализ ошибки: обращение к документу вместо табличной части

Начинающие разработчики часто выбирают в качестве источника данных саму таблицу документа Документ.ЗаказПокупателя и обращаются к его строкам через точку: ЗаказПокупателя.Товары.(...). При попытке применить предложение СГРУППИРОВАТЬ ПО к такому запросу платформа выдает ошибку:

«Группировка по таблицам верхнего уровня и вложенным таблицам»

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

Чтобы избежать подобных ошибок, обращаться в секции ИЗ нужно непосредственно к реальной таблице табличной части: Документ.ЗаказПокупателя.Товары. В этом случае каждая строка документа становится отдельной записью плоской таблицы, а реквизиты шапки (если они нужны) получаются через обращение к реквизиту Ссылка.

Различие между «СГРУППИРОВАТЬ ПО» и «ИТОГИ ПО»

Рассмотрим важный концептуальный момент языка запросов 1С:

  1. СГРУППИРОВАТЬ ПО — служит для схлопывания одинаковых строк и вычисления агрегатных функций (сумма, количество, максимум) на уровне единой плоской таблицы. Если в запросе выводятся уникальные детальные строки (артикул, номенклатура, количество), использование СГРУППИРОВАТЬ ПО по всем этим полям без агрегатных функций избыточно и лишь создает дополнительную нагрузку на СУБД.
  2. ИТОГИ ПО — строит иерархическую структуру результата запроса (дерево итогов). Именно это предложение позволяет сформировать структуру, в которой верхний уровень содержит группировочное поле (например, НоменклатурнаяГруппа), а подчиненный уровень — детальные записи номенклатурных позиций.

Корректный текст запроса для печатной формы

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


ВЫБРАТЬ
	ЗаказТовары.Номенклатура.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
	ЗаказТовары.Номенклатура.Артикул КАК Артикул,
	ЗаказТовары.Номенклатура КАК Номенклатура,
	ЗаказТовары.Номенклатура.ЕдиницаХраненияОстатков КАК ЕдиницаИзмерения,
	ЗаказТовары.Количество КАК Количество
ИЗ
	Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
ГДЕ
	ЗаказТовары.Ссылка = &Ссылка
УПОРЯДОЧИТЬ ПО
	НоменклатурнаяГруппа,
	Артикул,
	Номенклатура
ИТОГИ
	СУММА(Количество)
ПО
	НоменклатурнаяГруппа

Посмотрим на особенности этого запроса: секция ИТОГИ СУММА(Количество) ПО НоменклатурнаяГруппа автоматически посчитает общее количество товара в разрезе каждой группы, а сортировка обеспечит правильный алфавитный порядок вывода строк внутри групп.

Способ 1. Двухуровневый обход выборки с использованием итогов

Разберем по шагам, как вывести результат такого запроса в ТабличныйДокумент с отдельной строкой-заголовком для каждой номенклатурной группы:

  1. Получаем выборку верхнего уровня с помощью метода Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
  2. Во внешнем цикле выводим секцию шапки группы макета (например, ОбластьШапкаГруппы).
  3. Для каждой группы получаем вложенную детальную выборку методом ВыборкаГруппы.Выбрать(ОбходРезультатаЗапроса.Прямой).
  4. Во внутреннем цикле заполняем и выводим строки с номенклатурой (ОбластьСтрока).

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


Запрос = Новый Запрос;
Запрос.Текст = 
	"ВЫБРАТЬ
	|	ЗаказТовары.Номенклатура.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
	|	ЗаказТовары.Номенклатура.Артикул КАК Артикул,
	|	ЗаказТовары.Номенклатура КАК Номенклатура,
	|	ЗаказТовары.Количество КАК Количество
	|ИЗ
	|	Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
	|ГДЕ
	|	ЗаказТовары.Ссылка = &Ссылка
	|УПОРЯДОЧИТЬ ПО
	|	НоменклатурнаяГруппа,
	|	Артикул,
	|	Номенклатура
	|ИТОГИ
	|	СУММА(Количество)
	|ПО
	|	НоменклатурнаяГруппа";

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

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

ТабДокумент = Новый ТабличныйДокумент;
НомерСтроки = 0;

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

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

Способ 2. Линейный обход с отслеживанием смены группы

Если в запросе не используется секция ИТОГИ, мы можем решить эту задачу с помощью линейного обхода. Для этого достаточно отсортировать результат запроса по полю группировки и отслеживать момент изменения значения переменной в цикле:


Выборка = РезультатЗапроса.Выбрать();
ТекущаяГруппа = Неопределено;
НомерСтроки = 0;

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

Рекомендации по обработке крайних случаев

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

  1. Пустые значения групп: у товаров в базе реквизит НоменклатурнаяГруппа может быть не заполнен. При выводе таких строк обязательно предусматривайте подстановку понятного текста (например, «Без группы»), чтобы не выводить пустые строки-заголовки.
  2. Нумерация строк: если требуется сквозная нумерация по всему документу, инициализируйте счетчик НомерСтроки до начала внешнего цикла. Если нумерация должна начинаться с единицы внутри каждой группы — сбрасывайте значение переменной перед входом во внутренний цикл.
  3. Оптимизация обращений через точку: при обращении вида ЗаказТовары.Номенклатура.НоменклатурнаяГруппа платформа выполняет неявное левое соединение. В высоконагруженных отчетах рекомендуется использовать явные конструкции ЛЕВОЕ СОЕДИНЕНИЕ со справочником Справочник.Номенклатура.
← На главную