Вычисление сумм в 1С: от табличных частей до запросов и СКД
Содержание
Сумму в 1С считают в трёх местах, и способ зависит от того, что именно нужно. Итог по колонке открытого документа даёт метод Итог("Сумма") табличной части. Сводку по множеству записей из базы собирает запрос с агрегатной функцией СУММА. Отчёт с подытогами строят на системе компоновки данных (СКД) через ресурс. Отдельно стоит пересчёт сумм в валюте по курсу и подсчёт в простой арифметике встроенного языка.
Ниже разберём каждый способ с рабочим кодом: сложение чисел в модуле, группировку в запросе, итоги и ресурсы в отчётах, пересчёт валютных сумм и автоматический пересчёт итогов регистров. Отдельно — где расчёт тормозит и какие ошибки съедают копейки.
Итог по колонке табличной части и таблицы значений
Самый частый случай — посчитать сумму одной колонки у уже загруженной табличной части документа или у таблицы значений. Для этого есть готовый метод Итог("ИмяКолонки"), перебирать строки вручную не нужно.
// Сумма колонки "Сумма" в табличной части "Товары"
ИтогоПоДокументу = Объект.Товары.Итог("Сумма");
// То же для таблицы значений
ИтогоТЗ = ТаблицаТоваров.Итог("Цена");
Метод возвращает число — сумму всех значений колонки по текущему набору строк. Если колонка пустая, вернётся 0, а не NULL.
Если нужна сумма не по всем строкам, а по отбору (например, только по товарам одного склада), отфильтруйте данные заранее: скопируйте нужные строки во временную таблицу значений методом Скопировать() с отбором, а затем вызовите Итог() у копии.
В форме документа итог колонки обычно уже выводится в подвале таблицы автоматически — это настройка самой колонки формы, отдельный код для показа не нужен. Писать Итог() вручную стоит, когда сумма участвует в расчётах: заполняет реквизит шапки, проверяется перед проведением, попадает в печатную форму.
Арифметика во встроенном языке и тип Число
Для простого сложения работают обычные операторы: +, -, *, /. Накапливать сумму в переменной можно и в цикле, но у этого способа есть подводные камни.
Итог = 0;
Для Каждого Строка Из Объект.Товары Цикл
Итог = Итог + Строка.Количество * Строка.Цена;
КонецЦикла;
Главный нюанс — тип Число в 1С имеет длину (сколько всего разрядов) и точность (сколько из них после запятой). Эти параметры задаются у реквизита в конфигураторе. Если длина мала, крупная сумма просто обрежется до максимума, а лишние знаки после запятой отбросятся по точности.
Перед сложением денежных значений убедитесь, что у реквизита-приёмника хватает длины: для рублёвых сумм обычно 15 знаков и 2 знака после запятой.
Склеивать число со строкой напрямую нельзя — получите ошибку несовпадения типов. Для вывода в сообщение или печатную форму переводите число явно:
Сообщить("Итого: " + Формат(Итог, "ЧДЦ=2; ЧРД=,; ЧГ=' '"));
Здесь ЧДЦ=2 — два знака после запятой, ЧРД=, — запятая как разделитель дробной части, ЧГ=' ' — пробел между триадами. Без форматирования число подставится как есть, с точкой и без разделителей разрядов.
Функция СУММА и группировка в запросе
Когда данных много и они лежат в регистрах или документах базы, складывать их в цикле — медленно. Правильный инструмент — запрос с агрегатной функцией СУММА и группировкой.
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
СУММА(Продажи.СуммаОборот) КАК Сумма
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
Правило: каждое поле в списке выборки либо стоит под агрегатной функцией, либо перечислено в СГРУППИРОВАТЬ ПО. Нарушите его — платформа вернёт ошибку.
Кроме СУММА, в языке запросов есть КОЛИЧЕСТВО, МИНИМУМ, МАКСИМУМ и СРЕДНЕЕ. Для подсчёта числа строк в группе удобно КОЛИЧЕСТВО(*).
Агрегатные функции пропускают значения NULL. Но NULL появляется при соединениях: если во внешнем соединении у документа нет связанной строки, поле приходит пустым и ломает последующую арифметику. Страхуйтесь функцией ЕСТЬNULL:
СУММА(ЕСТЬNULL(СтрокиЗаказа.Сумма, 0)) КАК СуммаЗаказа
СУММА по пустому результату запроса вернёт NULL, а не ноль. Если читаете одно итоговое число из выборки, оберните его ЕСТЬNULL(..., 0) или проверяйте выборку на пустоту перед чтением.
Итоги в запросе и ресурсы в отчётах СКД
Для сводной таблицы с подытогами по группам в языке запросов есть конструкция ИТОГИ ... ПО. Она добавляет к выборке дополнительные строки-итоги, которые потом удобно обходить по уровням.
ВЫБРАТЬ
Продажи.Склад КАК Склад,
Продажи.Номенклатура КАК Номенклатура,
Продажи.СуммаОборот КАК Сумма
ИЗ
РегистрНакопления.Продажи.Обороты КАК Продажи
ИТОГИ
СУММА(Сумма)
ПО
ОБЩИЕ,
Склад
ОБЩИЕ даёт одну строку с суммой по всему результату, ПО Склад — промежуточные итоги на каждый склад. Обходят такой результат через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
Но для готовых отчётов ручной обход итогов почти не нужен — для этого есть СКД. В схеме компоновки данных заводится ресурс: поле, по которому система сама считает агрегат. Зайдите на вкладку Ресурсы конструктора схемы, добавьте поле суммы и задайте выражение Сумма(СуммаОборот).
После этого СКД сама расставит итоги по всем группировкам отчёта и общий итог внизу — независимо от того, как пользователь перестроит структуру в настройках. Это и есть штатный способ «работы с итогами» в пользовательских отчётах: ресурс считает, группировки задают разрезы.
Если нужен итог не простой суммой, а, например, суммой только по последней дате — в выражении ресурса СКД допустимы вычисления вида Сумма(...), Максимум(...) и обращение к функциям. Логику агрегата держите в ресурсе, а не в постобработке.
Суммы в валюте и пересчёт по курсу
Если сумма ведётся в валюте, к арифметике добавляется пересчёт в рубли. Курс и кратность хранятся в регистре сведений курсов валют на каждую дату. Формула одна:
Сумма в рублях = Сумма в валюте × Курс ÷ Кратность.
Кратность нужна для валют с мелким номиналом: курс может задаваться за 10 или 100 единиц. Делить на кратность обязательно, иначе сумма раздуется в десятки раз.
Пример: 1 200 USD при курсе 92,50 за 1 единицу (кратность 1) дадут 1 200 × 92,50 ÷ 1 = 111 000 ₽. Если бы курс задавался за 100 единиц (кратность 100) и равнялся 9 250, результат был бы тем же: 1 200 × 9 250 ÷ 100 = 111 000 ₽.
Курс на дату документа получают через менеджер регистра — срезом последних на нужную дату. В запросе это виртуальная таблица среза последних: берёте по валюте запись, актуальную на дату операции, и умножаете.
Курс берите на дату хозяйственной операции, а не на текущую. Для закрытых периодов пересчёт по сегодняшнему курсу исказит сумму и разойдётся с проводками. Курсовые разницы между датами — отдельная тема, в сам пересчёт суммы их не вмешивайте.
Запрос вместо цикла: как ускорить расчёт
Самое узкое место в расчёте сумм — попытка собрать итог обходом большой выборки в цикле, да ещё с запросом внутри. На сотнях строк разница незаметна, на сотнях тысяч — расчёт встаёт.
Антипаттерн, которого избегают:
// ПЛОХО: запрос внутри цикла
Для Каждого Строка Из СписокНоменклатуры Цикл
Запрос.УстановитьПараметр("Номенклатура", Строка.Номенклатура);
// ... выполнение запроса на каждой итерации
КонецЦикла;
Вместо этого складывайте на стороне СУБД: один запрос с СУММА и СГРУППИРОВАТЬ ПО вернёт готовые итоги разом. База делает агрегацию эффективнее, чем встроенный язык построчно.
Короткий чек ускорения:
- 🚀 суммируйте в запросе агрегатной функцией, а не в цикле обхода выборки;
- 🚀 не выполняйте запрос внутри цикла — передавайте список параметром и группируйте;
- 🚀 для регулярных сумм используйте виртуальные таблицы Обороты и ОстаткиИОбороты регистров — они уже агрегированы по хранимым итогам;
- 🚀 не тяните в выборку лишние поля: чем меньше колонок и соединений, тем быстрее агрегация.
Виртуальные таблицы оборотов регистров опираются на хранимые итоги, поэтому работают быстро — но только пока итоги рассчитаны. Об их пересчёте — в предпоследнем разделе.
Ошибки при подсчёте сумм и как их избежать
Расчёт суммы ломается тихо: цифра появляется, но неправильная. Разберём типовые причины.
Итог("ИмяКолонки") в модуле. Перебирать строки не нужно.СУММА(Поле) и СГРУППИРОВАТЬ ПО. Пустые значения оборачивайте в ЕСТЬNULL.Сумма(Поле). Итоги расставятся сами.Сумма × Курс ÷ Кратность на дату операции.Частые промахи:
- Потеря копеек на округлении. Округляйте функцией
Окр(Число, 2)— третий аргумент задаёт режим для «ровной половины»: по умолчанию1округляет вверх,0отбрасывает. Для денег фиксируйте режим явно, чтобы итог строк совпадал с итогом документа. - NULL вместо нуля.
СУММАпо пустой выборке даётNULL. Дальше любая арифметика сNULLснова даётNULL— итог обнуляется или выдаёт ошибку. ЛечитЕСТЬNULL. - Мало разрядов у типа Число. Длинная сумма обрезается по длине реквизита, дробная часть — по точности. Для рублёвых сумм закладывайте точность 2, для количеств — ту, что нужна учёту.
- Двойной счёт при соединениях. Если документ соединён с табличной частью, у которой несколько строк, шапочная сумма размножится по строкам и
СУММАеё задвоит. Агрегируйте на том уровне, где значение единственно, или используйте вложенный запрос. - Число склеено со строкой без
Формат. Приводит к ошибке типов или к сумме без разделителей. Выводите черезФормат(...)илиСтрШаблон.
Если итог «Количество × Цена» по строкам не сходится с итоговой суммой документа на копейку-две — почти всегда виноват разный порядок округления: где-то округляют каждую строку, где-то только общий итог. Решайте, что первично, и считайте единообразно.
Пересчёт итогов регистров по расписанию
Регистры накопления и бухгалтерии хранят промежуточные итоги — именно они делают виртуальные таблицы оборотов быстрыми. После массовых загрузок, перепроведения задним числом или сбоев итоги могут разойтись с фактическими движениями, и суммы в отчётах «поедут».
Пересчитать итоги
можно штатной обработкой Управление итогами (в клиент-серверном варианте — через раздел администрирования и обслуживания базы) либо программно методом менеджера регистра ПересчитатьИтоги().
Пересчёт итогов — тяжёлая операция, она надолго блокирует регистр. Запускайте её в нерабочее время и предупреждайте пользователей: во время пересчёта проведение документов по этому регистру будет ждать.
Чтобы не делать это руками, пересчёт выносят в регламентное задание. Создают его в конфигураторе (ветка общих объектов, регламентные задания) с процедурой-обработчиком, которая вызывает пересчёт, и задают расписание — например, ночью раз в неделю.
Важная оговорка по режиму базы:
- 🖥 В клиент-серверном варианте регламентные задания выполняет сервер самостоятельно, по расписанию, без открытого сеанса.
- 💾 В файловом варианте фоновые и регламентные задания работают только пока открыт хотя бы один сеанс и назначен механизм их выполнения — ночью при закрытой программе они не отработают.
Управлять списком заданий и смотреть историю запусков удобно в пользовательском режиме через раздел администрирования и обслуживания — там видно, когда задание отработало и не упало ли оно с ошибкой.
Частые вопросы
Чем `Итог()` отличается от запроса с `СУММА`?
Итог() считает сумму колонки у уже загруженной в память табличной части или таблицы значений — быстро и без обращения к базе. СУММА в запросе агрегирует данные на стороне СУБД по множеству записей из базы. Для открытого документа берите Итог(), для сводки по базе — запрос.Почему `СУММА` в запросе вернула пусто, хотя записи есть?
СУММА даёт NULL, оберните результат в ЕСТЬNULL(..., 0). Либо суммируемое поле само приходит как NULL из-за внешнего соединения — тогда ЕСТЬNULL нужен внутри: СУММА(ЕСТЬNULL(Поле, 0)).Как получить сумму и в валюте, и в рублях одним запросом?
СуммаВал * Курс / Кратность. Валютную и рублёвую суммы агрегируйте отдельными СУММА.Итог по строкам не сходится с суммой документа на копейку — это ошибка?
Можно ли вычислять сумму прямо в ресурсе отчёта?
Сумма(Поле), и система сама считает итоги по всем группировкам и общий итог, как бы пользователь ни перестроил отчёт.