Как сложить значения полей в запросе: СУММА, группировка и итоги

· 9 мин

Содержание

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

Разберём суммирование по шагам: как устроена функция СУММА, чем группировка по нескольким измерениям отличается от одного, когда вместо ручной агрегации выгоднее взять готовую виртуальную таблицу регистра, как считать сумму по условию через ВЫБОР КОГДА и как получить многоуровневые итоги секцией ИТОГИ. Отдельно — про NULL, из-за которого суммы молча «теряются».

Все примеры — на русском языке запросов платформы 8.3. Английские синонимы (SUM, GROUP BY) работают так же, но в типовых конфигурациях принят русский вариант.

Функция СУММА и другие агрегаты

СУММА() — агрегатная функция: она сворачивает множество строк в одно значение. Внутри скобок — числовое поле или выражение.

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

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

Рядом с СУММА в языке запросов есть ещё четыре агрегатные функции. Они подчиняются тем же правилам:

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

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

Сумму можно считать не только по полю, но и по выражению. Платформа сначала вычислит выражение в каждой строке, потом сложит:

ВЫБРАТЬ
    СУММА(Товары.Количество * Товары.Цена) КАК Стоимость
ИЗ
    Документ.ПоступлениеТоваровУслуг.Товары КАК Товары

Группировка по одному и нескольким измерениям

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

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

Группировка по одному измерению — сумма продаж по каждой номенклатуре:

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

Группировка по нескольким измерениям — сумма в разрезе «склад + номенклатура». В выборке два неагрегированных поля, оба уходят в группировку:

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

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

Отбор по результату агрегата (СУММА > 10000) нельзя поставить в ГДЕ — секция ГДЕ работает до группировки. Для фильтра по итогу используйте ИМЕЮЩИЕ.

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

Разница по секциям:

СекцияКогда применяетсяПо чему фильтрует
ГДЕдо группировкипо значениям строк
ИМЕЮЩИЕпосле группировкипо значению агрегата

Виртуальные таблицы вместо ручной суммы по регистру

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

  • 📊 РегистрНакопления.<Имя>.Обороты — сумма приходов и расходов за период.
  • 📦 РегистрНакопления.<Имя>.Остатки — остаток на дату.
  • 🔄 РегистрНакопления.<Имя>.ОстаткиИОбороты — и то, и другое.

Вместо того чтобы складывать строки движений самому:

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

берите готовый оборот — платформа посчитает его по итоговым таблицам регистра, а не перебором всех движений:

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

В виртуальной таблице ресурсы уже просуммированы — к имени ресурса платформа добавляет суффикс (СуммаОборот, КоличествоОстаток). Группировать и вызывать СУММА вручную не нужно, разрез задаётся параметрами и полями измерений.

Параметры виртуальной таблицы — период и отбор — ставьте прямо в скобках таблицы, а не в секции ГДЕ. Так отбор применяется при формировании виртуальной таблицы, до соединений, и запрос читает меньше данных.

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

Условное суммирование через ВЫБОР КОГДА

Частая задача — сложить не всё подряд, а по условию: отдельно приход и расход, отдельно продажи по одной организации. Аналог SUM(CASE WHEN ...) в 1С — функция ВЫБОР внутри СУММА.

Внутри агрегата выражение ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ 0 КОНЕЦ возвращает либо значение, либо ноль, и дальше суммируется:

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

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

Ссылку на предопределённое значение (вид движения, статус, значение перечисления) задавайте через ЗНАЧЕНИЕ(...), а не строкой — строковое сравнение имени не сработает.

Проверьте, что вы усвоили разницу между секциями:

Проверьте себя
Где фильтровать строки по сумме больше 10 000, если сумма — это результат СУММА()?
Что вернёт запрос с СУММА(), если в нём нет секции СГРУППИРОВАТЬ ПО и других полей?
Как в запросе сложить сумму только по приходным движениям?

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

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

ИТОГИ не заменяет группировку, а достраивает к детальным строкам дополнительные строки-итоги. Результат получается иерархическим — его удобно обходить деревом.

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

Что здесь происходит:

  1. Детальные строки остаются как есть — по каждой строке документа.
  2. Ключевое слово ОБЩИЕ добавляет одну строку с итогом по всему результату.
  3. Поле Организация после ПО даёт подытог по каждой организации.

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

Читать такой результат построчно бессмысленно — итоговые строки перемешаются с детальными. Обходите его по уровням группировок:

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

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

NULL при суммировании и типичные ошибки

NULL — не ноль и не пустая строка, а «значение отсутствует». В суммировании он ведёт себя неочевидно, и именно на нём чаще всего теряются деньги в отчёте.

Как СУММА обращается с NULL:

  • СУММА пропускает NULL — такие строки в сложение не входят. Если все значения в группе NULL, результат тоже NULL, а не ноль.
  • В арифметике любой NULL «заражает» выражение: 100 + NULL даёт NULL, а не 100. Это бьёт по суммам с умножением и по соединениям.
  • Чаще всего NULL приходит из ЛЕВОЕ СОЕДИНЕНИЕ: строки без пары во второй таблице отдают NULL во всех её полях.

Лечится одной функцией — ЕСТЬNULL(Поле, 0). Она подставляет ноль вместо отсутствующего значения до суммирования:

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

Диагностика частых сбоев при суммировании:

Диагностика: выберите симптом

Ещё две частые ошибки по сути, а не по синтаксису:

  • 🧮 Суммирование таблицы движений вместо виртуальной таблицы оборотов — запрос работает, но на большом регистре тормозит. Берите .Обороты.
  • 📐 Суммирование до соединения перепутано с суммированием после. Если соединение размножает строки, сначала сворачивайте в подзапросе, потом соединяйте, иначе сумма вырастет кратно.

Суммы по периодам: обороты и итоги по месяцам

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

Через периодичность виртуальной таблицы. У таблицы Обороты третий параметр — периодичность. Укажите Месяц, и платформа добавит в результат поле Период, а обороты разложит по месяцам:

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

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

Через ИТОГИ ПЕРИОДАМИ. Когда данные берутся из документа, а не из регистра, разложить сумму по месяцам помогает ИТОГИ ... ПЕРИОДАМИ:

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

Главное преимущество ПЕРИОДАМИ перед обычной группировкой по дате — платформа добавит строки-итоги даже для месяцев, в которых продаж не было. В отчёте не появится «дыр», и график строится ровно по всем периодам интервала.

Если нужна просто сумма уникальных значений (например, сумма по различным договорам без повторов), работает СУММА(РАЗЛИЧНЫЕ Поле) — повторяющиеся значения складываются один раз. Применяйте осознанно: чаще нужна обычная СУММА, а РАЗЛИЧНЫЕ — редкий случай.

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

Чем ИТОГИ отличаются от СГРУППИРОВАТЬ ПО?
СГРУППИРОВАТЬ ПО сворачивает строки в группы и возвращает плоскую таблицу. ИТОГИ ... ПО оставляет детальные строки и добавляет к ним иерархические строки-итоги — результат становится деревом, которое обходят по группировкам.
Как сложить два поля в одно значение?
Суммируйте выражение: СУММА(Таблица.Сумма1 + Таблица.Сумма2). Если какое-то из полей может быть NULL, оберните его в ЕСТЬNULL, иначе вся сумма строки станет NULL.
Почему СУММА возвращает NULL, а не ноль?
Если в группе все значения пустые (NULL), результат агрегата тоже NULL. Чтобы гарантировать число, примените ЕСТЬNULL(СУММА(Поле), 0) или оберните поле до суммирования.
Можно ли отфильтровать строки по итоговой сумме?
Да, но только через ИМЕЮЩИЕ — эта секция работает после группировки. В ГДЕ отбор по агрегату поставить нельзя, она применяется до свёртки.
Как посчитать сумму по условию, не делая два запроса?
Через СУММА(ВЫБОР КОГДА Условие ТОГДА Поле ИНАЧЕ 0 КОНЕЦ). В одной выборке так получают сразу несколько сумм по разным условиям — например, приход и расход в отдельных колонках.