Как правильно получить итоговую сумму из запроса 1С и вывести ее в переменную

Программист 1С v8.3 (Обычные формы) Управленческий учет
← На главную

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

Анализ исходной проблемы

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

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

Решение №1: Использование агрегатных функций в секции ВЫБРАТЬ

Если нашей целью является получение единственного значения (например, общей суммы по договору), самым правильным и производительным способом будет использование агрегатной функции СУММА непосредственно в полях выбора. Рассмотрим этот вариант подробнее:


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

Почему это лучше? В данном случае сервер базы данных (SQL) выполняет группировку самостоятельно и возвращает в 1С ровно одну строку с результатом. Это минимизирует трафик между сервером СУБД и сервером приложений 1С.

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


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

Если Выборка.Следующий() Тогда
    ИтоговаяСумма = Выборка.СуммаДоговора;
    Сообщить("Итого по договору: " + ИтоговаяСумма);
КонецЕсли;

Решение №2: Использование конструкции ИТОГИ ОБЩИЕ

Иногда нам нужно получить и детальные записи (например, список заказов), и общую сумму одновременно. В этом случае мы используем ИТОГИ. Чтобы получить доступ к самому верхнему уровню (сумме всех сумм), необходимо добавить ключевое слово ОБЩИЕ.


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

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


Результат = Запрос.Выполнить();
// Выбираем только уровень общих итогов
ВыборкаОбщая = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "ОБЩИЕ");

Если ВыборкаОбщая.Следующий() Тогда
    // Здесь лежит сумма всех сумм
    ОбщийИтог = ВыборкаОбщая.Сумма;
    Сообщить("Общий итог по всем заказам: " + ОбщийИтог);
КонецЕсли;

Оптимизация: переход на виртуальные таблицы

Проанализируем еще один важный аспект. В исходном запросе автор обращается к физической таблице РегистрНакопления.РасчетыСПокупателями. Для получения оборотов (суммы приходов) в 1С крайне рекомендуется использовать виртуальные таблицы, такие как Обороты. Это значительно ускоряет работу системы, так как 1С использует заранее рассчитанные итоги.

Перепишем запрос с использованием виртуальной таблицы:


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

Важные нюансы:

  1. Фильтрацию (Контрагент, Договор) следует передавать в параметры виртуальной таблицы, а не в секцию ГДЕ. Это позволит СУБД обработать меньше данных.
  2. В таблице оборотов уже есть разделение на Приход и Расход, поэтому условие на ВидДвижения становится ненужным.

Обработка пустых значений (NULL)

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

Рассмотрим, как обезопасить наш код с помощью функции ЕСТЬNULL:


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

Использование ЕСТЬNULL(..., 0) гарантирует, что если данных нет, мы получим число 0, с которым можно безопасно проводить математические операции в коде 1С.

Краткий итог и рекомендации

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

  1. Нужно только одно число: Используйте агрегатную функцию СУММА в полях выбора (секция ВЫБРАТЬ) и метод Выборка.Следующий() без циклов.
  2. Нужны итого и детализация: Используйте ИТОГИ ... ПО ОБЩИЕ, Группировка и обходите результат методом Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
  3. Всегда думайте о производительности: По возможности заменяйте запросы к физическим таблицам регистров на запросы к виртуальным таблицам Обороты или ОстаткиИОбороты.
  4. Безопасность типов: Оборачивайте суммируемые поля в ЕСТЬNULL, чтобы избежать проблем с неопределенными значениями в пустых результатах.

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

← На главную