ИТОГИ и иерархия

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

ИТОГИ ... ПО — конструкция запроса 1С, которая добавляет к детальным строкам результата дополнительные строки с промежуточными (по группам) и общим итогом.

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

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

Простой ИТОГИ

ВЫБРАТЬ
    Продажи.Склад КАК Склад,
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.Сумма КАК Сумма
ИЗ
    Документ.РеализацияТоваров.Товары КАК Продажи
ИТОГИ
    СУММА(Сумма)
ПО
    Склад

После ИТОГИ идут агрегаты, которые надо посчитать, после ПО — поля, по которым нужны подытоги. Здесь мы получим детальные строки продаж плюс строку-итог по каждому складу с суммой, плюс общий итог по всей выборке. Общий итог платформа добавляет всегда, даже без явного указания.

Уровней в ПО может быть несколько — тогда итоги будут вложенными: сначала общий, внутри — по каждому складу, внутри склада — по каждому товару.

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

Обход результата с итогами

Если такой результат обойти привычным Пока Выборка.Следующий() Цикл, вы получите вперемешку детальные и итоговые строки — разобрать их трудно. Поэтому результат с итогами обходят по уровням: сначала итоговые строки верхнего уровня, для каждой — вложенные, и так до деталей. Задаёт способ обхода метод Выбрать с параметром ОбходРезультата.

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

Результат = Запрос.Выполнить();

// Обходим верхний уровень — группировки по складам
ВыборкаСклад = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаСклад.Следующий() Цикл
    Сообщить("Склад: " + ВыборкаСклад.Склад + ", итого: " + ВыборкаСклад.Сумма);

    // Внутри склада обходим детальные строки
    ВыборкаСтрока = ВыборкаСклад.Выбрать();
    Пока ВыборкаСтрока.Следующий() Цикл
        Сообщить("    " + ВыборкаСтрока.Номенклатура + ": " + ВыборкаСтрока.Сумма);
    КонецЦикла;
КонецЦикла;

Результат:

Склад: Основной склад, итого: 45000
    Кофе молотый 250 г: 30000
    Чай чёрный 100 пак.: 15000
Склад: Розничный магазин, итого: 12000
    Кофе молотый 250 г: 12000

Разберём по частям. Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) даёт выборку верхнего уровня — по одной строке на склад, и в ней уже посчитанный итог Сумма. Внутри цикла ВыборкаСклад.Выбрать() без параметра открывает детальные строки, попавшие в этот склад. Так вложенными циклами мы обходим дерево: группировки снаружи, детали внутри. Значение системного перечисления ОбходРезультатаЗапроса.ПоГруппировкам и есть команда «иди по уровням итогов, а не подряд».

Второй режим — ОбходРезультатаЗапроса.ПоГруппировкамСИерархией — нужен, когда итоги строятся по иерархическому справочнику и надо спускаться по дереву групп. О нём — ниже.

Иерархические итоги по группам справочника

Справочники в 1С часто иерархические: у «Номенклатуры» есть группы «Напитки», «Кондитерские изделия», внутри — подгруппы и товары. Обычный ИТОГИ ПО Номенклатура посчитает подытог по каждому товару, но не свернёт их по группам-родителям. Чтобы получить итоги по веткам дерева справочника, после поля в секции ПО добавляют ключевое слово ИЕРАРХИЯ:

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

Слово ИЕРАРХИЯ после поля говорит платформе: строй итоги не только по конечным товарам, но и по их группам-родителям вверх по дереву. В результате появятся итоговые строки уровня «Напитки», «Кондитерские изделия» и внутри — по конкретным товарам. Обходят такой результат режимом ПоГруппировкамСИерархией:

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

Здесь вторым параметром "Номенклатура" указано поле, по чьей иерархии идём. Внутри каждой строки снова доступен .Выбрать() для спуска на уровень ниже — так обход и превращается в рекурсивный проход по дереву справочника.

Как это работает

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

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

Частые ошибки

Обходят итоги обычным циклом. Если после запроса с ИТОГИ написать простой Результат.Выбрать() и крутить Следующий(), вернутся только детальные строки — итоги как будто пропали. Они никуда не делись: чтобы их увидеть, нужен обход ПоГруппировкам. Это самая частая путаница с ИТОГИ.

Порядок полей в ПО не соответствует нужной вложенности. В ИТОГИ ... ПО Склад, Номенклатура внешний уровень — склад, внутренний — товар. Поменяете местами — получите итоги по товарам, а внутри по складам. Порядок задаёт дерево, следите за ним.

Забыли, что общий итог всегда есть. Верхний уровень обхода ПоГруппировкам начинается с общей итоговой строки по всей выборке. Если её не ждать, легко получить «лишнюю» на первый взгляд запись — это нормальный общий итог.

Ждут итоги в Выгрузить(). Результат.Выгрузить() отдаёт плоскую таблицу значений без итоговых строк. Нужны итоги — обходите результат по группировкам, а не выгружайте в таблицу.

Итоги

  • ИТОГИ агрегаты ПО поля добавляет к детальным строкам промежуточные подытоги и общий итог, не убирая детали.
  • Несколько полей в ПО дают вложенные уровни итогов; общий итог платформа добавляет всегда.
  • Результат с итогами обходят по уровням: Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), а внутри — вложенный .Выбрать().
  • Слово ИЕРАРХИЯ после поля строит итоги по группам иерархического справочника; обход — ПоГруппировкамСИерархией.
  • В плоскую таблицу Выгрузить() итоговые строки не попадают — они доступны только через обход по группировкам.
Проверьте себя
1. Запрос содержит ИТОГИ СУММА(Сумма) ПО Склад. Как обойти результат, чтобы увидеть итоговые строки по складам?
AОбычным циклом Пока Результат.Выбрать().Следующий()
BЧерез Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), а внутри — вложенный .Выбрать() для деталей
CЧерез Результат.Выгрузить() в таблицу значений
DИтоги видны только в консоли запросов, из кода их получить нельзя
2. Что добавляет слово ИЕРАРХИЯ в конструкции ИТОГИ СУММА(Сумма) ПО Номенклатура ИЕРАРХИЯ?
AСортирует итоги по алфавиту
BСчитает итоги ещё и по группам-родителям иерархического справочника, а не только по конечным элементам
CУскоряет выполнение запроса на больших данных
DУбирает из результата детальные строки, оставляя только итоги