Разработка эффективных отчетов в системе 1С:Предприятие часто требует консолидации разрозненных данных в единые итоговые показатели. Когда вы сталкиваетесь с необходимостью получить общую сумму документов, остаток товаров или начисления по сотрудникам, простого перебора строк в цикле часто бывает недостаточно из-за низкой производительности. Правильное использование возможностей встроенного языка запросов позволяет переложить вычислительную нагрузку на сервер базы данных, что критически важно при больших объемах информации.

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

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

Базовый синтаксис функции СУММА и группировка

Самый распространенный сценарий — получение итоговой суммы по определенному измерению. Для этого в списке полей запроса используется конструкция СУММА(Таблица.Поле) КАК Итог.

Рассмотрим типичный пример: нам нужно узнать общую сумму продаж по каждому менеджеру. В таблице документов есть поле "Менеджер" и поле "Сумма". Чтобы получить результат, мы должны сгруппировать данные по менеджеру. Синтаксически это выглядит так: после ключевого слова ВЫБРАТЬ мы указываем поля, а затем через запятую пишем агрегатную функцию.

Ключевое слово ПО указывает системе, по каким признакам объединять строки. Если вы укажете слишком много полей в группировке, детализация будет слишком высокой, и суммы не просуммируются так, как вы ожидали. Напротив, если убрать поле из группировки, но оставить его в выборке без агрегации, запрос не выполнится.

ВЫБРАТЬ

Продажи.Менеджер КАК Менеджер,

СУММА(Продажи.СуммаДокумента) КАК ОбщаяСумма

ИЗ

Документ.РеализацияТоваровУслуг КАК Продажи

ГДЕ

Продажи.Проведен = ИСТИНА

СГРУППИРОВАТЬ ПО

Продажи.Менеджер

В данном примере система отберет все проведенные документы, сгруппирует их по конкретному значению поля Менеджер и для каждой группы вычислит сумму поля СуммаДокумента. Результатом будет таблица, где каждому менеджеру соответствует одна строка с итоговой цифрой.

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

💡

Используйте псевдонимы (КАК..) для полей с агрегатными функциями. Это сделает код более читаемым и позволит легко обращаться к этим полям при дальнейшей обработке результата в коде 1С.

Использование оператора ИТОГИ для многоуровневого анализа

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

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

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

ВЫБРАТЬ

Продажи.Подразделение,

Продажи.Менеджер,

СУММА(Продажи.Сумма) КАК Сумма

ИЗ

Документ.РеализацияТоваровУслуг КАК Продажи

СГРУППИРОВАТЬ ПО

Продажи.Подразделение,

Продажи.Менеджер

ИТОГИ ПО

Подразделение,

Менеджер

В этом примере запрос вернет детальные строки по каждому менеджеру, затем строки с итогами по каждому подразделению (где поле Менеджер будет пустым или помеченным флагом) и, возможно, общую итоговую строку, в зависимости от настроек. Поле ТипЗаписи (или аналогичное, в зависимости от версии платформы) позволит отличить итог от детали.

Как отличить итоговую строку от детальной?

В результатах запроса с оператором ИТОГИ появляются виртуальные поля. Обычно это поля с именами совпадающими с полями группировки, но имеющие тип "Булево". Если значение ИСТИНА — это итоговая строка по данному измерению. Также может присутствовать поле "УровеньИтога", указывающее глубину вложенности.

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

💡

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

Работа с временными таблицами и промежуточное суммирование

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

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

Преимущество метода в том, что вы можете создать индекс по временной таблице перед финальной выборкой. Это критически важно для производительности, если объем промежуточных данных велик. Индексация ускоряет операцию группировки и соединения, если она потребуется далее.

// Шаг 1: Формирование детальной выборки

ВЫБРАТЬ

Номенклатура.Ссылка КАК Номенклатура,

Движения.Количество КАК Количество,

Движения.Сумма КАК Сумма

ПОМЕСТИТЬ ДетальныеДанные

ИЗ

РегистрНакопления.Продажи.Движения КАК Движения

ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура

ПО Движения.Номенклатура = Номенклатура.Ссылка

ГДЕ

Движения.Период МЕЖДУ &НачПериода И &КонПериода

;

// Шаг 2: Агрегация данных

ВЫБРАТЬ

ДетальныеДанные.Номенклатура,

СУММА(ДетальныеДанные.Количество) КАК ИтогоКоличество,

СУММА(ДетальныеДанные.Сумма) КАК ИтогоСумма

ИЗ

ДетальныеДанные

СГРУППИРОВАТЬ ПО

ДетальныеДанные.Номенклатура

Обратите внимание на использование ключевого слова ПОМЕСТИТЬ и завершающей точки с запятой ; после первого запроса. Это обязательный синтаксис для создания временной таблицы. Имя таблицы (ДетальныеДанные) затем используется во втором запросе как обычная таблица источника.

⚠️ Внимание: Временные таблицы занимают оперативную память сервера 1С. Не создавайте их без необходимости, если можно обойтись одним запросом. Всегда старайтесь отфильтровать данные максимально строго на этапе создания временной таблицы, чтобы не перегружать память лишними записями.

📊 Какой способ суммирования вы используете чаще?
Один сложный запрос
Временные таблицы
Цикл в коде 1С
СКД (Система Компоновки Данных)

Агрегация в объединении запросов (ОБЪЕДИНИТЬ)

Ситуации, когда данные для отчета находятся в разных таблицах или регистрах, требуют использования оператора ОБЪЕДИНИТЬ. Частая ошибка разработчиков заключается в попытке поставить СУММА() внутри каждой части объединения. Это неверно: сначала нужно собрать все строки в одну общую выборку, и только потом применять агрегацию к результату.

Правильный паттерн выглядит так: каждая часть запроса в объединении возвращает детальные строки с одинаковой структурой полей. Затем внешним запросом мы оборачиваем это объединение и суммируем поля. Это гарантирует, что строки с одинаковыми ключами из разных источников будут корректно сложены.

Если же просуммировать данные внутри каждой части ОБЪЕДИНИТЬ, вы получите несколько строк с одинаковыми ключами группировки (по одной от каждого источника), которые не сложатся между собой автоматически. Вам придется делать еще один уровень вложенности запроса.

ВЫБРАТЬ

Объединение.Контрагент,

СУММА(Объединение.СуммаДолга) КАК ОбщийДолг

ИЗ

(

ВЫБРАТЬ

Покупки.Контрагент,

Покупки.СуммаОстаток КАК СуммаДолга

ИЗ

РегистрБухгалтерии.Взаиморасчеты.Остатки КАК Покупки

ГДЕ

Покупки.СчетДт = &СчетПокупки

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ

Продажи.Контрагент,

-1 * Продажи.СуммаОстаток КАК СуммаДолга

ИЗ

РегистрБухгалтерии.Взаиморасчеты.Остатки КАК Продажи

ГДЕ

Продажи.СчетКт = &СчетПродажи

) КАК Объединение

СГРУППИРОВАТЬ ПО

Объединение.Контрагент

В примере выше мы объединяем дебетовые и кредитовые остатки. Обратите внимание на математику: кредитовый остаток умножается на -1, чтобы при суммировании получить сальдо (разницу). Внешний запрос суммирует эти значения по контрагенту, давая чистый долг или переплату.

☑️ Проверка запроса с ОБЪЕДИНИТЬ

Выполнено: 0 / 4

Обработка NULL значений при суммировании

Одной из подводных камней при работе с агрегатными функциями является поведение системы с пустыми значениями (NULL). Функция СУММА() игнорирует значения NULL. Если в поле, которое вы суммируете, у всех строк в группе стоит NULL, то результатом функции будет NULL, а не ноль.

Это может привести к ошибкам в дальнейшем коде или некорректному отображению в отчете (пустая ячейка вместо нуля). Для решения этой проблемы используется функция ЕСТЬNULL(). Она позволяет подменить значение NULL на указанное вами, например, на ноль.

Рекомендуется всегда оборачивать результат агрегатной функции в проверку на NULL, если ожидается, что результат может быть пустым. Это делает отчеты более предсказуемыми и избавляет от необходимости проверять тип значения в коде обработки результата.

ВЫБРАТЬ

Склад.Номенклатура,

ЕСТЬNULL(СУММА(Остатки.Количество), 0) КАК КоличествоОстаток

ИЗ

Справочник.Номенклатура КАК Склад

ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки КАК Остатки

ПО Склад.Ссылка = Остатки.Номенклатура

СГРУППИРОВАТЬ ПО

Склад.Номенклатура

В данном случае, даже если у товара не было движений и в регистре остатков нет записей (из-за ЛЕВОГО СОЕДИНЕНИЯ придет NULL), функция ЕСТЬNULL вернет 0. Это стандартная практика написания надежных запросов в 1С.

⚠️ Внимание: Функция ЕСТЬNULL() работает только с одним значением. Если вам нужно проверить результат сложного выражения, убедитесь, что вы оборачиваете в нее именно итоговое выражение, а не отдельные слагаемые, если логика этого требует.

Сравнение производительности: Запрос vs Цикл

Начинающие разработчики часто прибегают к выбору данных в объект или таблицу значений, а затем организуют цикл Для каждого для суммирования строк. Такой подход крайне неэффективен при работе с тысячами и миллионами записей. Каждое обращение к данным внутри цикла и управление коллекцией в памяти 1С consumes ресурсы значительно быстрее, чем выполнение одного оптимизированного запроса на стороне СУБД.

Серверы баз данных (MS SQL, PostgreSQL, Oracle), на которых работает 1С, специально созданы для быстрой агрегации данных. Они используют индексы, параллельное выполнение и оптимизацию планов запросов. Перекладывание этой задачи на клиент-сервер 1С лишает вас этих преимуществ.

Таблица ниже демонстрирует примерное сравнение подходов для выборки 100 000 строк:

Метод Время выполнения Нагрузка на память Рекомендация
Запрос с СУММА() 0.1 - 0.5 сек Минимальная Использовать всегда
Выборка + Цикл 5.0 - 15.0 сек Высокая Только для малых данных
СКД (виртуальная таблица) 0.2 - 0.6 сек Средняя Для пользовательских отчетов

Использование запроса также снижает сетевой трафик между сервером 1С и сервером базы данных, так как передается только итоговый результат (несколько строк), а не весь массив детальных записей.

💡

Никогда не суммируйте большие объемы данных в цикле на стороне кода 1С. Всегда используйте возможности языка запросов для агрегации на стороне СУБД.

Частые ошибки и способы их устранения

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

Другая ошибка — попытка использовать агрегатную функцию в секции ГДЕ. Условия фильтрации должны применяться к детальным данным до их группировки. Если нужно отфильтровать уже посчитанные суммы (например, показать только тех менеджеров, у которых сумма продаж больше миллиона), используйте секцию ИМЕЮЩИЕ.

Секция ИМЕЮЩИЕ работает аналогично ГДЕ, но применяется после группировки и вычисления агрегатных функций. Это позволяет эффективно отсекать ненужные итоги еще на уровне базы данных, не передавая лишние строки в приложение.

ВЫБРАТЬ

Менеджеры.ФИО,

СУММА(Продажи.Сумма) КАК СуммаПродаж

ИЗ

..

СГРУППИРОВАТЬ ПО

Менеджеры.ФИО

ИМЕЮЩИЕ

СУММА(Продажи.Сумма) > 1000000

Использование ИМЕЮЩИЕ критически важно для производительности, когда итоговая выборка должна быть небольшой, но исходные данные объемные. Это предотвращает передачу тысяч строк с малыми суммами, которые все равно будут отброшены программой.

Можно ли суммировать поля разных типов в одном запросе?

Нет, функция СУММА() применима только к числовым типам данных (Число). Попытка просуммировать строку или дату приведет к ошибке. Если нужно объединить строки, используйте функцию МАКСИМУМ() или МИНИМУМ(), либо конкатенацию, но не сумму.

Почему сумма в запросе отличается от суммы в документе?

Чаще всего это связано с округлением. В базе данных суммы могут храниться с высокой точностью (до 10 знаков после запятой), а в запросе или отчете отображаться с меньшей. Проверьте настройки округления и используйте функцию ОКРУГЛИТЬ() явно, если требуется строгое соответствие.

Как просуммировать данные из разных таблиц без объединения?

Если таблицы не связаны напрямую, но нужно получить общую сумму, можно использовать подзапросы в списке выражений или сложить результаты двух отдельных запросов в коде. Однако чаще всего требуется найти общий ключ для соединения или использования ОБЪЕДИНИТЬ ВСЕ.

Влияет ли индекс на скорость выполнения СУММА()?

Да, напрямую. Если группировка идет по полю, которое не индексировано, базе данных придется сканировать всю таблицю (Full Table Scan). Наличие индекса по полям группировки и фильтрации ускоряет агрегацию в разы.

Что делать, если запрос с ИТОГИ работает медленно?

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