Вычисление сумм в 1С: от табличных частей до запросов и СКД

· 9 мин

Содержание

Сумму в 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 ₽.

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

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

Запрос вместо цикла: как ускорить расчёт

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

Антипаттерн, которого избегают:

// ПЛОХО: запрос внутри цикла
Для Каждого Строка Из СписокНоменклатуры Цикл
    Запрос.УстановитьПараметр("Номенклатура", Строка.Номенклатура);
    // ... выполнение запроса на каждой итерации
КонецЦикла;

Вместо этого складывайте на стороне СУБД: один запрос с СУММА и СГРУППИРОВАТЬ ПО вернёт готовые итоги разом. База делает агрегацию эффективнее, чем встроенный язык построчно.

Короткий чек ускорения:

  • 🚀 суммируйте в запросе агрегатной функцией, а не в цикле обхода выборки;
  • 🚀 не выполняйте запрос внутри цикла — передавайте список параметром и группируйте;
  • 🚀 для регулярных сумм используйте виртуальные таблицы Обороты и ОстаткиИОбороты регистров — они уже агрегированы по хранимым итогам;
  • 🚀 не тяните в выборку лишние поля: чем меньше колонок и соединений, тем быстрее агрегация.

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

Ошибки при подсчёте сумм и как их избежать

Расчёт суммы ломается тихо: цифра появляется, но неправильная. Разберём типовые причины.

Где считать сумму — зависит от задачи

Частые промахи:

  1. Потеря копеек на округлении. Округляйте функцией Окр(Число, 2) — третий аргумент задаёт режим для «ровной половины»: по умолчанию 1 округляет вверх, 0 отбрасывает. Для денег фиксируйте режим явно, чтобы итог строк совпадал с итогом документа.
  2. NULL вместо нуля. СУММА по пустой выборке даёт NULL. Дальше любая арифметика с NULL снова даёт NULL — итог обнуляется или выдаёт ошибку. Лечит ЕСТЬNULL.
  3. Мало разрядов у типа Число. Длинная сумма обрезается по длине реквизита, дробная часть — по точности. Для рублёвых сумм закладывайте точность 2, для количеств — ту, что нужна учёту.
  4. Двойной счёт при соединениях. Если документ соединён с табличной частью, у которой несколько строк, шапочная сумма размножится по строкам и СУММА её задвоит. Агрегируйте на том уровне, где значение единственно, или используйте вложенный запрос.
  5. Число склеено со строкой без Формат. Приводит к ошибке типов или к сумме без разделителей. Выводите через Формат(...) или СтрШаблон.

Если итог «Количество × Цена» по строкам не сходится с итоговой суммой документа на копейку-две — почти всегда виноват разный порядок округления: где-то округляют каждую строку, где-то только общий итог. Решайте, что первично, и считайте единообразно.

Пересчёт итогов регистров по расписанию

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

Пересчитать итоги можно штатной обработкой Управление итогами (в клиент-серверном варианте — через раздел администрирования и обслуживания базы) либо программно методом менеджера регистра ПересчитатьИтоги().

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

Чтобы не делать это руками, пересчёт выносят в регламентное задание. Создают его в конфигураторе (ветка общих объектов, регламентные задания) с процедурой-обработчиком, которая вызывает пересчёт, и задают расписание — например, ночью раз в неделю.

Важная оговорка по режиму базы:

  • 🖥 В клиент-серверном варианте регламентные задания выполняет сервер самостоятельно, по расписанию, без открытого сеанса.
  • 💾 В файловом варианте фоновые и регламентные задания работают только пока открыт хотя бы один сеанс и назначен механизм их выполнения — ночью при закрытой программе они не отработают.

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

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

Чем `Итог()` отличается от запроса с `СУММА`?
Итог() считает сумму колонки у уже загруженной в память табличной части или таблицы значений — быстро и без обращения к базе. СУММА в запросе агрегирует данные на стороне СУБД по множеству записей из базы. Для открытого документа берите Итог(), для сводки по базе — запрос.
Почему `СУММА` в запросе вернула пусто, хотя записи есть?
Либо выборка после отборов оказалась пустой — тогда СУММА даёт NULL, оберните результат в ЕСТЬNULL(..., 0). Либо суммируемое поле само приходит как NULL из-за внешнего соединения — тогда ЕСТЬNULL нужен внутри: СУММА(ЕСТЬNULL(Поле, 0)).
Как получить сумму и в валюте, и в рублях одним запросом?
Соедините данные с виртуальной таблицей среза последних регистра курсов на дату операции и добавьте вычисляемое поле СуммаВал * Курс / Кратность. Валютную и рублёвую суммы агрегируйте отдельными СУММА.
Итог по строкам не сходится с суммой документа на копейку — это ошибка?
Нет, это разный порядок округления. Если округлять каждую строку и потом складывать, итог может отличаться от округления общей суммы. Выберите одну схему и применяйте её везде — расхождение уйдёт.
Можно ли вычислять сумму прямо в ресурсе отчёта?
Да, это и есть штатный способ в СКД. В выражении ресурса пишете Сумма(Поле), и система сама считает итоги по всем группировкам и общий итог, как бы пользователь ни перестроил отчёт.