Итог по группе не равен сумме строк под ним, и отчёт при этом исправен

03.09.26

Разработка - СКД

Выражение ресурса в СКД компилируется, отрабатывает и не выдаёт ни одной ошибки, а число на уровне группы получается неверным, и заметить это можно только сверкой итога с суммой строк под ним. Разобрались, что возвращает конструкция: ВычислитьВыражениеСГруппировкойМассив честно перебирает номенклатуру, а вложенное ВычислитьВыражение со смещением "Предыдущая" на каждом шаге отвечает одно и то же, потому что считается в контексте категории. Массив выходит из одинаковых чисел, и вместо ожидаемых 4 получается -106. Пока обе функции работают по одной оси, разницы не видно, и конструкция годами живёт в отчётах. В кросс-таблице оси разные. Дальше - почему компилятор это пропускает, рецепт воспроизведения на четырёх строках литералов, схема переноса расчёта в запрос и одна гипотеза, которую я в той же ветке снял.

Механизм расхождения: ВВГМ перебирает номенклатуру по строкам, вложенное ВычислитьВыражение смещается по колонкам года, и потому остаётся снаружи перебора

 

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

Вот такой случай. Кросс-таблица: по строкам номенклатура внутри категории, по колонкам годы. Считается прирост количества год к году, причём приростом считается только тот случай, когда в предыдущем году было не ноль. По номенклатуре всё верно. На уровне категории - мусор.

Выражение ресурса для категории выглядит так:

СУММА(ВычислитьВыражениеСГруппировкойМассив(
    "ВЫБОР КОГДА ВычислитьВыражение(""ЕстьNULL(Сумма(Количество), 0)"", ""Год"", ,
        ""Предыдущая"", ""Предыдущая"", ""Год"") <> 0
     ТОГДА ЕстьNULL(Сумма(Количество), 0) - ВычислитьВыражение(""ЕстьNULL(Сумма(Количество), 0)"",
        ""Год"", , ""Предыдущая"", ""Предыдущая"", ""Год"")
     ИНАЧЕ 0 КОНЕЦ",
    "Номенклатура"))

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

Разбор ниже: что именно возвращает эта конструкция, почему платформа на ней не ругается, как проверить у себя за пять минут и чем лечится.

Сразу про источник, чтобы не было вопросов. Случай пришёл с форума Инфостарта, тема 343053, где я отвечал в августе. Разбор механизма мой, воспроизведение и проверка на своих наборах - автора темы, он там под ником rossin. Я на этом настаиваю потому, что дальше будет тезис, который я же там и снял, а такое лучше рассказывать самому.

 

Что возвращает массив

Первое, что стоит сделать при разборе, - убрать внешнюю СУММА и посмотреть на сам массив. Автор темы так и поступил.

Ожидание: массив из отклонений по каждой номенклатуре категории. Скажем, две позиции: у первой было 10, стало 14, отклонение +4; у второй было 100, стало 100, отклонение 0. Итог по категории должен быть 4.

Что приходит на деле: массив из одинаковых элементов, и каждый равен величине, посчитанной по всей категории целиком. Не по позиции. То есть если в категории за прошлый год суммарно 110, то вложенный вызов вернёт 110 и для первой номенклатуры, и для второй.

Дальше арифметика беспощадна: (14 - 110) + (100 - 110) = -106 вместо ожидаемых 4. Знак другой, порядок другой, и это ещё удачный случай - расхождение видно глазом. При других данных разница может выйти правдоподобной, и тогда её никто не поймает.

 

Механизм: две функции сужают контекст по разным осям

Теперь почему так.

ВычислитьВыражениеСГруппировкойМассив действительно перебирает записи названной ей группировки. В нашем случае - "Номенклатура". Для каждой записи она вычисляет переданное выражение и складывает результаты в массив. Пока всё как ожидается.

А вот вложенное ВычислитьВыражение со смещением "Предыдущая" отсчитывает позицию не внутри этого перебора. Оно отсчитывает её по структуре той группировки, для которой пишется само выражение ресурса. Ресурс мы пишем для категории, значит и смещение считается в контексте категории.

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

Ключевое следствие, ради которого стоит это запомнить:

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

В кросс-таблице оси разные. Номенклатура в строках, год в колонках. Перебор идёт по одной оси, смещение по другой, и вложенный вызов просто остаётся снаружи перебора.

 

Почему компиляция это пропускает

Вопрос законный: если конструкция бессмысленна, почему платформа не ругается.

Потому что она не бессмысленна синтаксически. Первый аргумент ВВГМ - строка. Внутри неё лежит выражение на языке СКД, и это выражение само по себе корректно: ВычислитьВыражение с существующей группировкой и допустимыми видами границ. Компилятор проверяет, что группировка "Год" в отчёте есть, что смещение "Предыдущая" - валидное значение, что скобки сходятся. Всё сходится.

Вопрос "а к какому контексту относится это смещение при переборе" компилятором не задаётся вовсе. Он и не должен: контекст определяется в момент вычисления, а не в момент разбора.

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

 

Как проверить у себя

Рецепт на пять минут, без наших данных и без нашей конфигурации.

  1. Сделайте отчёт на запросе из четырёх строк литералами: две номенклатуры, два года, количества 10 и 14 для первой, 100 и 100 для второй.
  2. Структура - таблица: в строках категория и вложенная в неё номенклатура, в колонках год.
  3. Ресурс отклонения задайте дважды: для группировки "Номенклатура" - обычным ВычислитьВыражение со смещением, для группировки "Категория" - тем же выражением, завёрнутым в ВВГМ по номенклатуре.
  4. Сформируйте и сравните строку категории с суммой строк под ней.

Если ваш итог по категории равен 4 - у вас всё сходится, и дальше можно не читать. Если -106, вы воспроизвели ровно тот случай.

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

 

Чем лечится

Коротко: расчёт переносится в запрос, и СКД перестаёт быть местом, где считается логика.

Схема, которую я предложил в той теме и которую автор в итоге внедрил:

  1. Собрать полный крест измерений: список всех лет периода на список всей номенклатуры. Это обязательный шаг, а не украшение. В исходном наборе строк с нулём просто нет, и случай "закупали 5, стало 0" без креста в отчёт не попадёт вообще.
  2. Двумя левыми соединениями подтянуть количество текущего года и количество предыдущего, условие соединения по году со сдвигом на единицу.
  3. Прямо в запросе посчитать поле отклонения:
ВЫБОР
    КОГДА ЕСТЬNULL(КоличествоПред, 0) <> 0
        ТОГДА ЕСТЬNULL(Количество, 0) - ЕСТЬNULL(КоличествоПред, 0)
    ИНАЧЕ 0
КОНЕЦ КАК Отклонение

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

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

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

 

Гипотеза, которую я снял

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

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

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

 

Что показал наш собственный прогон

Воспроизвести случай я решил программно: собрать схему компоновки прямо из кода и скомпоновать на тестовой УТ 11.5, платформа 8.3.27.1606. Данные те же четыре строки литералами. Вышло не то, что ожидалось, и это стоит рассказать целиком.

Сам ВВГМ работает как написано в документации. Ресурс СУММА(ВычислитьВыражениеСГруппировкойМассив("Сумма(Количество)", "Номенклатура")) на уровне категории даёт 224 - это 24 по первой позиции плюс 200 по второй, то есть перебор действительно идёт по номенклатуре и складывается правильно.

Вложенный вызов без второго аргумента тоже работает. Если внутрь положить ВычислитьВыражение("Сумма(Количество)"), результат тот же 224. Значит вложенный вызов честно сужается контекстом перебора, пока ему не назвали чужую группировку.

А вот с именем группировки компоновка падает:

Выражение не может быть вычислено "Сумма(Н.Кол), Год"

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

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

Практический вывод для тех, кто собирает СКД программно: вложенные вызовы с указанием группировки в выражениях ресурсов из кода не задавайте. Проверить это у себя дешевле, чем ловить потом: полминуты на схему из четырёх строк.

 

Границы применимости

Случай разобран на кросс-таблице. Утверждение "оси разные, поэтому не сужается" проверено на структуре, где номенклатура в строках, а год в колонках. Прочие сочетания - вложенность на одной ветке, несколько уровней колонок, группировки с иерархией - в этом разборе не проверялись.

Механизм я вывел разбором, а не своим прогоном на этой конструкции. Автор темы подтвердил его своими наборами данных и наблюдением за массивом. Свой прогон у меня есть, но он показал другое, и об этом раздел выше.

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

Версия. Разговор шёл в августе 2026 года на актуальной на тот момент платформе. Что будет на 8.3.28, никто не знает, и проверять это должен тот, кто на неё перейдёт.

 

Открытый вопрос

Меня в этой истории занимает не столько сам механизм, сколько то, что его нигде нет. Автор темы написал прямо: перечитал всё, от ИТС и документации до тем на Инфостарте и Мисте, и нигде не сказано ни что так работает, ни что так не работает. Я тоже не нашёл. Каждая функция описана сама по себе, а про их композицию не написано ничего.

Вопрос к тем, кто много работает с СКД: где вообще проходит граница разумного во вложенных выражениях ресурсов? Есть позиция "в ресурсах вообще ничего сложнее суммы делать не надо, всё в запрос" - и в ней много правды, но она стоит усложнения запроса на каждый чих. Есть противоположная: СКД для того и даёт эти функции, чтобы не плодить соединения. Где вы для себя проводите черту и по какому признаку?

Другие наши инструменты:

  • Трансформатор SQL в 1С - когда отчёт не только врёт, но и тормозит: переводит запрос из профайлера в имена справочников и регистров.
  • УНИЧТОЖИТЬ в конце пакета - разбирает пакетный запрос и показывает, где временная таблица висит дольше, чем нужна. Как раз про схему с крестом и двумя соединениями.
  • Чек-ап СУБД под 1С - если после переноса расчёта в запрос отчёт стал тяжелее, вопросы уже к настройкам сервера.
  • Карта объёмов базы 1С - что занимает место в базе, если крест по всем измерениям вдруг оказался слишком большим.

Вступайте в нашу телеграмм-группу Инфостарт

СКД ВычислитьВыражение ВычислитьВыражениеСГруппировкойМассив кросс-таблица отчёты ресурсы группировки диагностика

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта 1С:Предприятие 8 Платные (руб)

Инструменты для разработчиков 1С 8.3 и 8.5: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.

16500 руб.

02.09.2020    276003    1544    423    

1193

СКД Программист 1С:Предприятие 8 Бесплатно (free)

Статья написана по результатам проведенного внутреннего обучающего вебинара для разработчиков ГК «СофтБаланс». Если осилить 25 000 знаков - задача для вас непосильная, где-то на бескрайних просторах интернета видео есть (или будет). Но здесь информация точнее. Разберем, чем запрос для СКД принципиально отличается от обычного запроса и как модифицируется в зависимости от настроек. Изучим «базовый рецепт» написания запроса для СКД, сформируем чек-лист. Полезно будет всем – от стажеров до тех. лидов. Всем, кто не снимает галку «автозаполнение» и пишет запросы для отчетов в консоли запросов – читать (вдумчиво) обязательно.

29.10.2025    24904    ovetgana    112    

118

Инструментарий разработчика Запросы Программист 1С:Предприятие 8 1С:Зарплата и кадры государственного учреждения 3 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)

QueryConsole1C — расширение, включающее консоль запросов с поддержкой исполняемых представлений — аналогов виртуальных таблиц, основанных на методах программного интерфейса ЗУП. Оно позволяет выполнять запросы с учётом встроенной бизнес-логики, отлаживать алгоритмы получения данных и автоматически генерировать код на встроенном языке 1С.

1 стартмани

16.05.2025    12830    162    zup_dev    32    

86

СКД Программист 1С:Предприятие 8 Бесплатно (free)

Хорошая отчетная форма - сродни искусству. Есть какое-то невероятное эстетическое удовольствие в том, чтобы разобраться в логике учета и анализируемых показателях, спроектировать архитектуру хранения данных так, чтобы оптимально собрать эти показатели вместе с аналитическими разрезами в запросе, а затем настроить отображение так, чтобы, глядя на результат, сразу было понятно, что это за отчет и какие задачи он призван решать. Система компоновки данных - это моя первая, главная и, наверное, единственная "рабочая" любовь. Ее я использую везде, где только можно и где нельзя тоже. Хочу поделиться с вами некоторыми практическими приемами в работе с отчетами на СКД, которые, надеюсь, будут полезны.

27.02.2025    18931    ovetgana    50    

94

Запросы Программист 1С:Предприятие 8 1C:Бухгалтерия Бесплатно (free)

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

11.10.2024    23210    XilDen    39    

114

Инструментарий разработчика СКД Программист 1С:Предприятие 8 1C:Бухгалтерия Абонемент ($m)

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

3 стартмани

05.02.2024    14311    83    obmailok    21    

87

СКД WEB-интеграция Программист 1С:Предприятие 8 1C:Бухгалтерия Абонемент ($m)

Долгое время поддерживаю web-портал, в котором появилась необходимость создавать отчеты. Просмотрев различные фреймворки на js, я решил сделать свое решение, которое позволяло бы быстро разрабатывать и добавлять новые отчеты на web-портал.

2 стартмани

11.12.2023    17344    30    John_d    30    

129

Инструментарий разработчика СКД Программист 1С:Предприятие 8 Абонемент ($m)

DSL для работы с СКД.

1 стартмани

15.11.2023    12453    21    kalyaka    5    

99
Для отправки сообщения требуется регистрация/авторизация