Суммирование строк табличной части в 1С 8.3

· 9 мин

Содержание

Чтобы сложить значения колонки в табличной части, вызовите метод Итог(): строка Объект.Товары.Итог("Сумма") вернёт сумму по всем строкам — без цикла и без запроса. Это самый короткий способ просуммировать строки в 1С, когда данные уже загружены в объект или таблицу значений.

Но задача «получить сумму» встречается в разных местах: в модуле объекта при проведении, в запросе с группировкой по складу или номенклатуре, в подвале таблицы на форме, где итог должен пересчитываться при каждом изменении суммы в строке. Для каждого случая — свой инструмент. Ниже разберём четыре рабочих способа сложить строки табличной части, когда суммировать нужно не все строки, как учесть NULL и округление, и на каких мелочах чаще всего спотыкаются.

Код проверен на платформе 8.3 (интерфейс «Такси»), конфигурации значения не имеет — приёмы одинаковы в Бухгалтерии, ЗУП, УТ и собственных разработках.

СпособКогда применятьНа что смотреть
Итог("Колонка")данные уже в памяти, нужна одна цифраколонка должна быть типа Число
Запрос СУММАданные в базе, нужны группы или большой объёмполя вне агрегата — в СГРУППИРОВАТЬ ПО
Цикл Для Каждогопо ходу нужна проверка или несколько итоговобнулить накопитель, не менять коллекцию
Подвал формыпоказать итог пользователю, пересчёт на летуреквизит + ПутьКДаннымПодвала
Ресурс СКДитоги в отчётевыражение Сумма(Поле), итоги автоматом

Метод Итог() — сумма колонки одной строкой

У табличной части и у таблицы значений есть готовый метод Итог(). Он складывает все значения указанной колонки и возвращает число:

// Табличная часть документа "Товары", колонка "Сумма"
ИтогоПоДокументу = Объект.Товары.Итог("Сумма");

То же самое для таблицы значений, которую вы собрали в коде:

ОбщаяСумма = ТЗ.Итог("Сумма");

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

Имя колонки в кавычках регистронезависимо: Итог("Сумма") и Итог("СУММА") дадут одинаковый результат. А вот опечатка в самом имени (Итог("Сумаа")) вызовет ошибку «Колонка не найдена».

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

Сумма в запросе: агрегатная функция СУММА

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

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

Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
ИтогоСумма = Выборка.ИтогоСумма;

Обёртка ЕСТЬNULL(СУММА(...), 0) нужна не для пропуска пустых ячеек — СУММА и так игнорирует NULL внутри колонки. Она страхует от ситуации, когда под условие ГДЕ не попало ни одной строки: тогда сам агрегат вернёт NULL, и без обёртки в переменную попадёт не ноль, а неопределённое значение.

Промежуточные итоги по группам

Чаще нужна не одна цифра, а суммы в разрезе — по номенклатуре, складу, ставке НДС. Для этого добавляют группировку:

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

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

Нужна и детализация, и общий итог в одном результате — используйте конструкцию ИТОГИ:

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

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

"...ГДЕ Товары.Ссылка.Дата МЕЖДУ &Начало И &Конец
|СГРУППИРОВАТЬ ПО Товары.Ссылка.Организация"

Запрос — предпочтительный способ на больших объёмах: база складывает числа на своей стороне и отдаёт готовый результат, не перегоняя тысячи строк в память клиента. Для одного документа разница незаметна, но на отчёте по сотням документов она решает.

Складываем значения в цикле Для каждого

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

ИтогоСумма = 0;
Для Каждого СтрокаТовара Из Объект.Товары Цикл
	ИтогоСумма = ИтогоСумма + СтрокаТовара.Сумма;
КонецЦикла;

Переменную-накопитель обязательно обнуляют до цикла. Если ИтогоСумма осталась с прошлого вызова (например, внутри другого цикла по документам), итог «поедет».

Перебор удобен, когда условий несколько и каждое влияет на свой итог:

СуммаСоСкидкой = 0;
СуммаБезСкидки = 0;
Для Каждого СтрокаТовара Из Объект.Товары Цикл
	Если СтрокаТовара.ПроцентСкидки > 0 Тогда
		СуммаСоСкидкой = СуммаСоСкидкой + СтрокаТовара.Сумма;
	Иначе
		СуммаБезСкидки = СуммаБезСкидки + СтрокаТовара.Сумма;
	КонецЕсли;
КонецЦикла;

Внутри Для Каждого нельзя добавлять и удалять строки той же коллекции, по которой идёт обход, — это рушит перебор. Если нужно удалять строки по условию, перебирайте индексом в обратную сторону: Для Сч = Объект.Товары.Количество() - 1 По 0 Цикл.

Для одной колонки цикл проигрывает Итог() и в скорости, и в читаемости. Пишите его только там, где без построчной логики не обойтись.

Итог в подвале таблицы на форме и пересчёт на лету

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

  1. Добавьте на форму реквизит типа Число, например ИтогоСумма.
  2. У элемента-таблицы в свойствах включите Подвал.
  3. У колонки «Сумма» в свойстве ПутьКДаннымПодвала укажите этот реквизит ИтогоСумма. Горизонтальное положение подвала выровняйте по колонке.
  4. Напишите процедуру пересчёта и вызывайте её при открытии и при изменении суммы в строке.
&НаСервере
Процедура ПересчитатьИтог()
	ИтогоСумма = Объект.Товары.Итог("Сумма");
КонецПроцедуры

&НаКлиенте
Процедура ТоварыСуммаПриИзменении(Элемент)
	ПересчитатьИтог();
КонецПроцедуры

Обработчик ПриИзменении навешивается на поле «Сумма» внутри таблицы (событие колонки, не всей таблицы). Этот же ПересчитатьИтог() зовут из ПриСозданииНаСервере, чтобы итог был заполнен сразу при открытии документа.

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

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

Сумма не по всем строкам: отбор, условие, свёртка

Когда суммировать нужно подмножество строк, есть три подхода, кроме цикла с Если.

Отбор запросом. Условие уходит в ГДЕ — самый дешёвый вариант, если данные в базе:

"...ГДЕ Товары.Ссылка = &Ссылка И Товары.СтавкаНДС = &Ставка"

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

ТЗ = Объект.Товары.Выгрузить();
ТЗ.Свернуть("Склад", "Сумма");
// в ТЗ осталось по одной строке на склад с просуммированной колонкой "Сумма"

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

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

Свернуть умеет складывать сразу несколько колонок и группировать по нескольким полям:

ТЗ = Объект.Товары.Выгрузить();
ТЗ.Свернуть("Склад, Номенклатура", "Количество, Сумма");
// по каждой паре "склад + номенклатура" свёрнуты обе колонки

Итоги в отчёте на системе компоновки данных

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

Сумма(Сумма)

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

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

Особые случаи: NULL, Неопределено, типы и округление

Суммирование ломается чаще всего не на логике, а на данных.

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

NULL в запросе. Внутри колонки СУММА пропускает NULL сама. Проблема одна — пустая выборка: нет строк под условием, значит агрегат вернёт NULL. Всегда оборачивайте результат в ЕСТЬNULL(СУММА(...), 0).

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

// округляем готовый итог до копеек
ИтогоСумма = Окр(Объект.Товары.Итог("Сумма"), 2);

Третий параметр Окр() задаёт режим округления (0 — к меньшему по модулю, 1 — к большему); по умолчанию используется округление «как в арифметике».

Склейка с текстом. Выводя сумму в сообщение, не приклеивайте число к строке напрямую — используйте Формат или СтрШаблон:

Сообщить(СтрШаблон("Итого по документу: %1", Формат(ИтогоСумма, "ЧДЦ=2; ЧГ=0")));

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

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

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

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