Каждый разработчик на платформе 1С рано или поздно сталкивается с загадочным поведением запросов: в консоли запросов или при выгрузке результата в плоскую таблицу значений все данные заполнены и отображаются корректно, но стоит начать выводить их в макет (табличный документ) через выборку — и вместо реальных значений программа возвращает NULL.
В этой статье мы подробно разберем, почему возникает эта проблема, как устроена работа с выборками по группировкам в 1С и рассмотрим три эффективных способа решения этой задачи.
Проанализируем ситуацию, описанную в исходном коде. Разработчик обходит результат запроса с помощью метода РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) и на каждом шаге цикла пытается получить значения детальных полей:
Область.Параметры.ЗначениеГрСтрПостав = ВхЗапрВыб.НоменклатураПоставщика;
Когда вы используете обход по группировкам, переменная выборки (в нашем случае ВхЗапрВыб) на каждом уровне иерархии позиционируется на строке группировки, а не на детальной записи. На уровне группировки (например, на уровне «Номенклатура» или «Поставщик») платформе доступны только два типа полей:
ИТОГИ применены агрегатные функции, такие как СУММА, МАКСИМУМ, МИНИМУМ, СРЕДНЕЕ и так далее.Любые другие реквизиты, которые вы просто выбрали в секции ВЫБРАТЬ запроса (например, НоменклатураПоставщика, ВариантИсполненияПоставщика), на уровне группировки не существуют. Платформа не знает, какое именно из детальных значений сопоставить итоговой строке, поэтому возвращает значение NULL. Они станут доступны только тогда, когда обход опустится до самого нижнего уровня — уровня детальных записей.
Давайте рассмотрим подробнее, какими способами можно решить эту проблему.
Если для каждой группировки (например, для конкретного варианта исполнения или номенклатуры) детальное поле однозначно (то есть у одной номенклатуры может быть только один поставщик или одна номенклатура поставщика), самым простым и производительным решением будет «поднять» эти значения на уровень группировок.
Для этого нам нужно объявить эти поля ресурсами в секции ИТОГИ запроса с помощью функций МАКСИМУМ или МИНИМУМ. Поскольку это строковые или ссылочные типы данных, эти функции отработают корректно и вернут нужное значение на уровне группировок.
Разберем по шагам, как изменить секцию ИТОГИ вашего запроса:
// БЫЛО в запросе:
// ИТОГИ СУММА(КолПотребностьВнешнаяя)... ПО ОБЩИЕ {... Поставщик.*, НоменклатураПоставщика, ВариантИсполненияПоставщика}
// НАДО СДЕЛАТЬ:
ИТОГИ
СУММА(КолПотребностьВнешнаяя),
// ... другие ваши ресурсы
МАКСИМУМ(НоменклатураПоставщика),
МАКСИМУМ(ВариантИсполненияПоставщика),
МАКСИМУМ(Поставщик)
ПО ОБЩИЕ
{ИТОГИ ПО Номенклатура.*, ВариантИсполнения.*}
После добавления полей в расчет итогов через функцию МАКСИМУМ, обращение к свойству ВхЗапрВыб.НоменклатураПоставщика на уровне группировки «Номенклатура» перестанет возвращать NULL и вернет корректное строковое или ссылочное значение.
Бывают ситуации, когда изменять секцию ИТОГИ нежелательно (например, если у одной группировки может быть несколько разных поставщиков и функция МАКСИМУМ исказит логику отчета). В этом случае мы можем прямо внутри цикла обхода группировок обратиться к подчиненным детальным записям.
Для этого мы вызываем метод Выбрать() без параметров у текущей группировочной выборки. Это вернет нам выборку детальных записей, подчиненных текущему элементу дерева группировок. Нам достаточно прочитать первую детальную запись.
Посмотрим на пример реализации этого механизма в коде вывода:
Если _ИмяГр = "Номенклатура" И НЕ ВхЗапрВыб.РазрешитьНесколькоВариантовИсполнения Тогда
// Получаем подчиненную выборку детальных записей для текущей номенклатуры
ВыборкаДетальные = ВхЗапрВыб.Выбрать();
Если ВыборкаДетальные.Следующий() Тогда
Область.Параметры.ЗначениеГрСтрПостав = ВыборкаДетальные.НоменклатураПоставщика;
Иначе
Область.Параметры.ЗначениеГрСтрПостав = "";
КонецЕсли;
ИначеЕсли _ИмяГр = "ВариантИсполнения" Тогда
// Аналогично получаем данные для варианта исполнения
ВыборкаДетальные = ВхЗапрВыб.Выбрать();
Если ВыборкаДетальные.Следующий() Тогда
Область.Параметры.ЗначениеГрСтрПостав = ВыборкаДетальные.ВариантИсполненияПоставщика;
Иначе
Область.Параметры.ЗначениеГрСтрПостав = "";
КонецЕсли;
КонецЕсли;
Важное примечание: Этот способ работает медленнее первого, так как платформе приходится выполнять дополнительные позиционирования в курсоре выборки. Используйте его тогда, когда количество выводимых строк невелико, либо когда логика требует точечного анализа детальных записей.
В коде автора темы используются объекты ТипИзмеренияПостроителяОтчета, что явно указывает на применение механизма ПостроительОтчета. Если вы формируете отчет с помощью построителя, у вас есть возможность автоматически привязывать детальные реквизиты к измерениям строк.
Для этого в объекте ПостроительОтчета существует коллекция дополнительных полей для каждого измерения строки. Если мы настроим их программно, построитель сам позаботится о выводе этих данных на нужном уровне иерархии, избавив нас от необходимости вручную писать обходы.
Проанализируем пример настройки построителя перед выполнением отчета:
// Находим нужное нам измерение в строках построителя
ИзмерениеНоменклатура = ПостроительОтчета.ИзмеренияСтроки.Найти("Номенклатура");
Если ИзмерениеНоменклатура <> Неопределено Тогда
// Добавляем реквизит в коллекцию дополнительных полей группировки
ИзмерениеНоменклатура.ДополнительныеПоля.Добавить("НоменклатураПоставщика");
КонецЕсли;
После такой настройки при выводе группировки «Номенклатура» свойство НоменклатураПоставщика автоматически поднимется на нужный уровень иерархии и будет доступно в выборке.
Мы разобрали три различных подхода к устранению проблемы NULL значений при выводе группировок в 1С. Для большинства задач оптимальным решением является Способ №1 (использование агрегатной функции МАКСИМУМ в итогах), так как он работает на уровне СУБД, не перегружает сервер приложений 1С и гарантирует максимальное быстродействие отчета.