Группировка и подсчёт сумм в запросах и отчётах 1С

· 9 мин

Содержание

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

Выбор способа зависит от задачи. Готовый отчёт с настройками для пользователя — это СКД. Данные внутри обработки или кода — обычный запрос. Регистр накопления или бухгалтерии — виртуальная таблица, она агрегирует быстрее и правильнее ручного суммирования.

Чтобы сразу сориентироваться, где какой инструмент применяют:

СпособКогда применятьЧто возвращает
СГРУППИРОВАТЬ ПО в запросеДанные нужны в коде обработкиСвёрнутые строки без детализации
Ресурсы и группировки СКДОтчёт для пользователя с настройкамиОтчёт с итогами по уровням
Виртуальные таблицы регистровОстатки и обороты регистраУже агрегированные данные
Секция ИТОГИ … ПОНужны и детали, и итоги сразуДетальные строки плюс итоги
Свернуть() таблицы значенийДанные уже собраны в памятиСвёрнутую таблицу значений

Разберём каждый способ с рабочими примерами на языке запросов 8.3.

Агрегатные функции и секция СГРУППИРОВАТЬ ПО

Основной инструмент агрегации в запросе — агрегатные функции. Их пять:

  • 💰 СУММА() — сумма числовых значений;
  • 🔢 КОЛИЧЕСТВО() — число записей (есть вариант КОЛИЧЕСТВО(РАЗЛИЧНЫЕ …));
  • ⬆️ МАКСИМУМ() и ⬇️ МИНИМУМ() — крайние значения;
  • ⚖️ СРЕДНЕЕ() — среднее арифметическое.

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

Простейший пример — продажи по номенклатуре из регистра накопления:

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

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

КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Контрагент) считает число уникальных контрагентов, а не строк. Это частый приём, когда нужно узнать, скольким клиентам продали товар, а не сколько было продаж.

Фильтр по итогу: ГДЕ и ИМЕЮЩИЕ

Эти две секции легко перепутать, а результат они дают разный:

  • ГДЕ отбирает строки до агрегации — работает с исходными записями;
  • ИМЕЮЩИЕ отбирает после агрегации — работает с уже посчитанными итогами.

Чтобы оставить только номенклатуру с суммой продаж больше 100 000, фильтр ставят на агрегат через ИМЕЮЩИЕ:

СГРУППИРОВАТЬ ПО
    Продажи.Номенклатура
ИМЕЮЩИЕ
    СУММА(Продажи.Сумма) > 100000

Попытка написать то же условие в ГДЕ приведёт к ошибке: там агрегатные функции недопустимы.

Ресурсы и группировки в системе компоновки данных

В отчёте на СКД агрегатные функции обычно не пишут руками. Вместо этого поле помечают как ресурс, а строки сворачивают группировки в настройках.

Порядок такой:

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

Дальше СКД сама агрегирует ресурсы на каждом уровне группировки и формирует общий итог. Один и тот же набор детальных данных пользователь сможет свернуть по-разному — поменяв группировки в режиме предприятия, без правки запроса.

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

Выражения ресурсов в СКД богаче, чем в запросе: там доступны Вычислить, ВычислитьВыражениеСГруппировкойМассив, проценты от итога. Но базовая агрегация — это всё те же Сумма, Количество, Максимум, Минимум, Среднее.

Виртуальные таблицы регистров: агрегация без GROUP BY

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

  • Остатки — остаток на дату;
  • Обороты — приход и расход за период;
  • ОстаткиИОбороты — всё вместе с разбивкой по периодам.

Виртуальная таблица оборотов сама группирует движения и позволяет задать периодичность. Пример — обороты продаж по номенклатуре за период с помесячной разбивкой:

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

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

Отбор лучше передавать в параметрах виртуальной таблицы, а не в секции ГДЕ внешнего запроса. В параметрах он уменьшает объём данных ещё на уровне построения виртуальной таблицы.

Многоуровневые итоги: секция ИТОГИ

Если нужны не только итоги по группам, но и общий итог с иерархией, в запросе есть отдельная секция ИТОГИ … ПО. Она не заменяет СГРУППИРОВАТЬ ПО, а достраивает к детальным строкам дополнительные итоговые.

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

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

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

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

Агрегация при выводе в табличный документ

В самом табличном документе данные не суммируются — он только отображает уже посчитанное. Но у него есть группировки строк с возможностью сворачивать и разворачивать блоки, и это удобный способ показать итоги с детализацией.

Группы строк создают программно вокруг уже выведенных секций макета:

ТабДок.НачатьГруппуСтрок("ГруппаСклад", Истина);
    // вывод строки с итогом по складу и детальных строк
ТабДок.ЗакончитьГруппуСтрок();

Второй параметр — признак, открыта группа изначально или свёрнута. Логика простая: источник данных (запрос с итогами или выборка СКД) даёт и итоговые, и детальные значения, а табличный документ раскладывает их по группам, чтобы пользователь сворачивал ненужное.

Если отчёт строится на СКД, группировки в табличный документ попадают автоматически из настроек — руками НачатьГруппуСтрок писать не нужно. Ручной вывод остаётся для отчётов, собираемых без компоновщика.

Как ускорить агрегацию на больших данных

Медленная агрегация — почти всегда следствие того, что суммирование вынесли из СУБД в код. Основные приёмы:

  • 🚀 Агрегируйте в запросе, а не в цикле. Складывать суммы перебором строк таблицы значений или выборки — худший вариант: данные гоняются по сети, цикл работает на одном потоке. СУММА() в запросе выполняет сервер СУБД.
  • 📊 Для регистров берите виртуальные таблицы. Обороты и Остатки агрегируют на уровне хранилища и используют итоги регистра, если они включены.
  • 🔗 Избегайте задвоения при соединениях. Если соединить таблицу оборотов с табличной частью «один ко многим» и после этого суммировать, суммы умножатся на число строк соединения. Агрегируйте каждую таблицу отдельно во временных таблицах, потом соединяйте итоги.
  • 🧊 Выносите тяжёлые наборы во временные таблицы через ПОМЕСТИТЬ и индексируйте поля соединения. Это снижает нагрузку на СУБД при сложных отчётах.
  • 🎯 Фильтруйте до агрегации. Условие на период и организацию уменьшает объём данных до того, как платформа начнёт сворачивать строки.

Включённые итоги регистра накопления ускоряют чтение остатков, но замедляют запись движений и занимают место. Для регистров, по которым остатки почти не читают, а пишут много, итоги иногда отключают осознанно — это решение администратора, а не настройка по умолчанию.

Агрегация таблицы значений без запроса

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

Метод Свернуть() группирует строки прямо в таблице: первый параметр — колонки группировки, второй — колонки суммирования.

// свернуть по номенклатуре, просуммировав количество и сумму
ТаблицаПродаж.Свернуть("Номенклатура", "Количество, Сумма");

После вызова в таблице останется по одной строке на номенклатуру с суммированными числовыми колонками. Метод меняет исходную таблицу на месте — если нужен и детальный, и свёрнутый вариант, работайте с копией через Скопировать().

Общий итог по колонке считает метод Итог():

ВсегоСумма = ТаблицаПродаж.Итог("Сумма");

Свёртка таблицы значений хороша для небольших наборов данных, уже лежащих в памяти. Если данные ещё в базе, выгоднее агрегировать запросом на сервере СУБД, а не тянуть все строки в таблицу значений ради Свернуть().

Какой способ агрегации выбрать под задачу?

Ошибки, из-за которых агрегация даёт не тот результат

Разберём не повтор шагов, а именно сбои, на которых чаще спотыкаются:

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

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

NULL ломает сумму или среднее. СУММА() и СРЕДНЕЕ() пропускают NULL, но если NULL участвует в арифметике до агрегации (Поле1 + Поле2), результат всей строки станет NULL. Оборачивайте поля в ЕСТЬNULL(Поле, 0) перед сложением.

В СКД вместо суммы — одно значение. Поле числовое, а итог в группе равен значению одной строки. Причина — поле не помечено ресурсом. Вынесите его на вкладку Ресурсы.

Ручной свод движений регистра. Запрос с СГРУППИРОВАТЬ ПО по таблице РегистрНакопления.Продажи вместо .Обороты работает, но медленно и может не учесть логику активности движений. Для регистров почти всегда берут виртуальную таблицу.

Проверьте себя
В какой секции запроса ставят условие на итоговую сумму группы?
Что произойдёт, если числовое поле в СКД не сделать ресурсом?
Чем ИТОГИ отличается от СГРУППИРОВАТЬ ПО?

Частые вопросы

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