При разработке отчетов и обработок в среде «1С:Предприятие 8» программисты часто сталкиваются с необходимостью построения сложных иерархических структур данных. Использование конструкции ИТОГИ в языке запросов — это мощный инструмент для группировки данных на различных уровнях. Однако возникает закономерный вопрос: как узнать точное количество элементов, входящих во внутреннюю (вложенную) группировку? Нужно ли для этого обходить всю выборку циклом и вручную инкрементировать счетчик, или платформа предоставляет более элегантные встроенные механизмы?
В рамках данной статьи мы подробно разберем все возможные варианты решения этой задачи, проанализируем особенности работы с выборками запроса, изучим требования стандартов разработки «1С» и научимся оптимизировать сложные запросы.
Начнем с самого простого и очевидного программного способа. В платформе «1С:Предприятие 8» для работы с результатами запроса используется объект ВыборкаИзРезультатаЗапроса. У этого объекта есть встроенный метод Количество(). Но здесь кроется важный нюанс, который часто вызывает непонимание у начинающих разработчиков.
Проанализируем ситуацию: метод Количество() возвращает число записей только для текущего активного уровня выборки. Он не показывает общее количество детальных записей, находящихся на самых нижних уровнях иерархии, если мы находимся на верхнем уровне.
Рассмотрим подробнее на практическом примере. Представим, что у нас есть иерархический запрос, группирующий товары по складам:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладах.Склад КАК Склад,
| ТоварыНаСкладах.Номенклатура КАК Номенклатура,
| ТоварыНаСкладах.ВНаличии КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах
|ИТОГИ
| СУММА(Количество)
|ПО
| Склад,
| Номенклатура";
РезультатЗапроса = Запрос.Выполнить();
Если мы выгрузим этот запрос с обходом по группировкам, то для получения количества номенклатурных позиций на конкретном складе нам потребуется сначала обратиться к выборке по складам, а затем получить вложенную выборку по номенклатуре. Посмотрим на пример кода обхода:
ВыборкаСклады = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Склад");
Пока ВыборкаСклады.Следующий() Цикл
// Здесь Количество() вернет количество уникальных складов во всей выборке
ОбщееКоличествоСкладов = ВыборкаСклады.Количество();
// Теперь получаем подчиненную выборку для текущего склада
ВыборкаНоменклатура = ВыборкаСклады.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Номенклатура");
// А вот здесь метод Количество() вернет число уникальных позиций номенклатуры
// именно на текущем складе!
КоличествоНоменклатурыНаСкладе = ВыборкаНоменклатура.Количество();
Сообщить("На складе " + ВыборкаСклады.Склад + " находится видов номенклатуры: " + КоличествоНоменклатурыНаСкладе);
КонецЦикла;
Таким образом, нам не требуется перебирать вложенные элементы в цикле Пока ВыборкаНоменклатура.Следующий() только ради подсчета их количества. Платформа делает это автоматически при инициализации подчиненной выборки. Метод Количество() отрабатывает мгновенно и возвращает точное число элементов на данном уровне группировки.
Иногда получать количество элементов программным путем при обходе выборки неудобно — например, когда эти данные должны быть сразу выведены в табличный документ или переданы во внешнюю систему в виде готовой таблицы значений. В этом случае подсчет логично переложить на сторону СУБД.
Некоторые разработчики пытаются решить эту задачу "в лоб", создавая громоздкие конструкции во временных таблицах с предварительной группировкой и последующим левым соединением. Давайте проанализируем пример неоптимального запроса, часто встречающийся в практике:
// Пример неоптимального запроса с избыточными соединениями
ВЫБРАТЬ
АвансовыйОтчетТовары.Ссылка.Склад КАК Склад,
АвансовыйОтчетТовары.Номенклатура КАК Номенклатура,
СУММА(АвансовыйОтчетТовары.Количество) КАК Количество,
СУММА(АвансовыйОтчетТовары.Сумма) КАК Сумма
ПОМЕСТИТЬ втДанные
ИЗ
Документ.АвансовыйОтчет.Товары КАК АвансовыйОтчетТовары
СГРУППИРОВАТЬ ПО
АвансовыйОтчетТовары.Ссылка.Склад,
АвансовыйОтчетТовары.Номенклатура
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
взКоличествоНоменклатураНаСкладе.Склад КАК Склад,
взКоличествоНоменклатураНаСкладе.Номенклатура КАК Номенклатура,
взКоличествоНоменклатураНаСкладе.КоличествоНоменклатурыНаСкладе КАК КоличествоНоменклатурыНаСкладе
ПОМЕСТИТЬ втКоличествоНоменклатураНаСкладе
ИЗ
(ВЫБРАТЬ
втДанные.Склад КАК Склад,
втДанные.Номенклатура КАК Номенклатура,
СУММА(1) КАК КоличествоНоменклатурыНаСкладе
ИЗ
втДанные КАК втДанные
СГРУППИРОВАТЬ ПО
втДанные.Склад,
втДанные.Номенклатура) КАК взКоличествоНоменклатураНаСкладе
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
втДанные.Склад КАК Склад,
втДанные.Номенклатура КАК Номенклатура,
втДанные.Количество КАК Количество,
втДанные.Сумма КАК Сумма,
втКоличествоНоменклатураНаСкладе.КоличествоНоменклатурыНаСкладе КАК КоличествоНоменклатурыНаСкладе
ИЗ
втДанные КАК втДанные
ЛЕВОЕ СОЕДИНЕНИЕ втКоличествоНоменклатураНаСкладе КАК втКоличествоНоменклатураНаСкладе
ПО втДанные.Склад = втКоличествоНоменклатураНаСкладе.Склад
И втДанные.Номенклатура = втКоличествоНоменклатураНаСкладе.Номенклатура
ИТОГИ
СУММА(Количество),
СУММА(Сумма),
МАКСИМУМ(КоличествоНоменклатурыНаСкладе)
ПО
ОБЩИЕ,
Склад
Такой подход сильно перегружает СУБД, усложняет чтение кода и замедляет выполнение программы. Выясним, как сделать это гораздо проще. Язык запросов «1С» поддерживает использование агрегатных функций непосредственно в секции расчета итогов. Мы можем применить функцию КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле).
Разберем по шагам правильное и лаконичное построение запроса, которое решает ту же задачу без использования временных таблиц и левых соединений:
ВЫБРАТЬ
АвансовыйОтчетТовары.Ссылка.Склад КАК Склад,
АвансовыйОтчетТовары.Номенклатура КАК Номенклатура,
АвансовыйОтчетТовары.Количество КАК Количество,
АвансовыйОтчетТовары.Сумма КАК Сумма
ИЗ
Документ.АвансовыйОтчет.Товары КАК АвансовыйОтчетТовары
ИТОГИ
СУММА(Количество),
СУММА(Сумма),
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура) КАК КоличествоНоменклатурыНаСкладе
ПО
ОБЩИЕ,
Склад
При таком подходе платформа «1С» самостоятельно сформирует оптимальный SQL-запрос. На уровне общих итогов поле КоличествоНоменклатурыНаСкладе покажет общее число уникальных товаров по всем складам. На уровне группировки Склад это же поле будет содержать количество уникальных товаров, хранящихся именно на данном складе.
В процессе обсуждения подобных задач на форумах нередко звучит совет: "Добавьте в выборку техническую колонку со значением 1, а в итогах пропишите СУММА(ИмяПоля)". На первый взгляд, это кажется простым лайфхаком, но делать так категорически не рекомендуется.
Давайте разберем по шагам, почему этот прием опасен. Согласно официальным стандартам разработки фирмы «1С» (в частности, стандарт №std787 «Вычисление количества записей в запросах»), использование конструкции СУММА(1) или суммирование статических единиц несет в себе серьезные риски:
КОЛИЧЕСТВО(*)) гораздо лучше, чем искусственные математические операции над константами.Если вам действительно необходимо посчитать количество строк по определенному условию, когда стандартное КОЛИЧЕСТВО(*) неприменимо, обязательно используйте явное приведение типов с помощью оператора ВЫРАЗИТЬ. Посмотрим на пример безопасного кода:
// Правильный подсчет с расширением разрядности для предотвращения переполнения
СУММА(ВЫБОР
КОГДА АвансовыйОтчетТовары.Сумма > 1000
ТОГДА ВЫРАЗИТЬ(1 КАК ЧИСЛО(17, 0))
ИНАЧЕ 0
КОНЕЦ)
Такой подход гарантирует стабильную работу вашей конфигурации даже при многократном росте объема информационной базы.
Если ваша задача состоит в выводе количества элементов во вложенных группировках отчета, то использовать секцию ИТОГИ в тексте запроса не следует. Специфика работы СКД такова, что компоновщик данных самостоятельно управляет расчетом итогов, и жестко прописанная секция ИТОГИ в запросе может привести к ошибкам компоновки или неверному расчету результатов.
Посмотрим, как решить эту задачу стандартными средствами СКД без изменения структуры самого запроса:
Номенклатура).Количество(Различные Номенклатура).Склад).Если вам требуется вывести только итоговое количество элементов без демонстрации самих детальных записей, настройте структуру отчета следующим образом: в настройках структуры выберите группировку Склад, а вложенную группировку (детальные записи) просто не добавляйте. Также можно на вкладке «Выбранные поля» детальной группировки снять флаг «Авто» и очистить список выводимых полей.
Для более сложных аналитических расчетов, когда требуется агрегировать результаты вложенных группировок (например, посчитать среднее количество номенклатурных позиций на один склад), в СКД применяется мощная встроенная функция:
ВычислитьВыражениеСГруппировкойМассив()
Она позволяет собрать данные вложенных уровней в массив прямо в формуле ресурса и произвести над ними любые математические операции.
Мы детально рассмотрели три основных способа получения количества элементов во внутренних группировках: программный метод выборки, агрегатные функции в запросе с итогами и декларативные механизмы СКД. Выбор конкретного метода зависит от архитектуры вашего решения, но всегда важно помнить о стандартах разработки «1С» для обеспечения высокой производительности и надежности вашей системы.