Почему не работает группировка во вложенных таблицах запроса 1С и как исправить ошибку «Поле объекта не обнаружено»?

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

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

Разберем причины возникновения ошибок

Проанализируем ситуацию, которая произошла в исходном коде, и выделим две принципиальные проблемы: на уровне языка запросов и на уровне работы с объектом ВыборкаИзРезультатаЗапроса во встроенном языке 1С.

  1. Ограничение синтаксиса вложенных таблиц. Конструкция вида ВЫБРАТЬ ДокументПечать.Товары.(Номенклатура, Количество) формирует так называемую вложенную таблицу. Платформа 1С накладывает жесткое синтаксическое ограничение: во вложенных таблицах нельзя использовать агрегатные функции (СУММА, КОЛИЧЕСТВО), а также конструкции СГРУППИРОВАТЬ ПО и УПОРЯДОЧИТЬ ПО. Сгруппировать данные внутри круглых скобок вложенной таблицы невозможно, как невозможно и указать в секции группировки основного запроса поля из вложенной выборки. Именно поэтому платформа выдает ошибку «Группировка по таблицам верхнего уровня и вложенным таблицам» или «Поле не найдено».
  2. Изменение структуры результата запроса. В исходной процедуре печати обращение к строкам документа выполнялось через подчиненную выборку: ТЧастьДокумента = ВыборкаШапки.Товары.Выбрать(). Такой синтаксис допустим только тогда, когда в секции ВЫБРАТЬ используется обращение через точку с круглыми скобками Документ.Товары.(...). Если же мы переписываем запрос напрямую к реальной таблице базы данных ИЗ Документ.ОтчетОРозничныхПродажах.Товары, результат запроса становится плоским (одноуровневым). В объекте выборки больше нет колонки-таблицы с именем Товары, поэтому попытка обратиться к свойству .Товары немедленно вызывает ошибку времени выполнения.

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

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

Посмотрим на пример правильного текста запроса:


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

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

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

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


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

Решение 2. Сохранение двухуровневой выборки через конструкцию ИТОГИ ПО

Что делать, если в типовой процедуре печати жестко заложена логика иерархического обхода: сначала выбираются данные шапки документа, а затем внутри цикла перебираются строки? Переписывать весь механизм формирования макета не обязательно — используем предложение ИТОГИ ПО.

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


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

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

В этом случае обход дерева результатов в коде выглядит следующим образом:


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

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

Решение 3. Группировка данных через ТаблицуЗначений без изменения запроса

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


// Выгружаем табличную часть документа в отдельную таблицу значений
ТаблицаТоваров = ДокументОбъект.Товары.Выгрузить();

// Добавляем колонку для группировки, если реквизита нет в табличной части напрямую
ТаблицаТоваров.Колонки.Добавить("ВидНоменклатуры");
Для Каждого СтрокаТЧ Из ТаблицаТоваров Цикл
    СтрокаТЧ.ВидНоменклатуры = СтрокаТЧ.Номенклатура.ВидНоменклатуры;
КонецЦикла;

// Выполняем группировку: первый параметр - группировочные поля, второй - суммируемые
ТаблицаТоваров.Свернуть("ВидНоменклатуры", "Количество, Сумма");

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

Особенности арифметических действий в СУММА(А + Б)

При составлении итоговых выражений в запросе часто требуется просуммировать результат сложения или умножения нескольких полей (например, стоимость с учетом скидки или наценки). Конструкция СУММА(ПолеА + ПолеБ) поддерживается платформой и не требует обязательного создания подзапросов.

Однако если в результате получается пустое значение (NULL) или некорректная цифра, причина заключается в особенностях работы СУБД со значениями NULL: любое арифметическое сложение с пустым значением дает результат NULL (например, 100 + NULL = NULL).

Чтобы избежать ошибок при расчетах, всегда оборачивайте числовые поля в функцию ЕСТЬNULL:


ВЫБРАТЬ
    ТЧ.Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,
    СУММА(ЕСТЬNULL(ТЧ.Сумма, 0) + ЕСТЬNULL(ТЧ.СуммаНДС, 0)) КАК ВсегоСНДС
ИЗ
    Документ.ОтчетОРозничныхПродажах.Товары КАК ТЧ
ГДЕ
    ТЧ.Ссылка = &ТекущийДокумент
СГРУППИРОВАТЬ ПО
    ТЧ.Номенклатура.ВидНоменклатуры

Такой синтаксис гарантирует, что даже при отсутствии значения в одной из колонок арифметическое действие выполнится корректно.

← На главную