Почему при добавлении расчетных полей в запрос к таблице Остатки и обороты меняются значения остатков

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

При работе с виртуальными таблицами регистров накопления, такими как ОстаткиИОбороты, начинающие разработчики часто сталкиваются с парадоксом: добавление нового вычисляемого поля в секцию ВЫБРАТЬ внезапно меняет итоговые значения начальных или конечных остатков, которые до этого казались верными. Особенно ярко это проявляется, когда в параметрах виртуальной таблицы указана периодичность Регистратор.

В этой статье мы подробно разберем, почему так происходит, как платформа 1С интерпретирует запросы к виртуальным таблицам и как правильно организовать получение данных, чтобы избежать «разваливания» итогов.

Анализируем причину проблемы: коварство периодичности Регистратор

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

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

  1. Начальный остаток — это остаток в регистре непосредственно «до» проведения документа.
  2. Конечный остаток — это остаток сразу «после» проведения документа.

Проанализируем ошибку в секции итогов. В запросе автора используется конструкция ИТОГИ СУММА(КоличествоНачальныйОстаток) ПО ОБЩИЕ. Это логическая ловушка. Если за период было 10 документов, платформа просто сложит 10 значений остатков на разные моменты времени. Полученная сумма не будет иметь никакого физического смысла, так как начальный остаток для всего периода — это только остаток перед самым первым документом.

Влияние разыменования ссылок через точку

Теперь выясним, почему значения меняются именно при добавлении выражений вроде ИсточникДанных.ХарактеристикаНоменклатуры.ЮС_ВесВставок. В языке запросов 1С обращение к реквизитам через точку (разыменование ссылки) приводит к неявному левому соединению (LEFT JOIN) с таблицей соответствующего справочника на уровне СУБД.

Когда мы просто выбираем поля из виртуальной таблицы, 1С может использовать оптимизированные алгоритмы получения итогов. Однако, как только появляется вычисляемое выражение с участием полей из других таблиц, СУБД вынуждена строить более сложный план запроса. Рассмотрим, что происходит:

  1. Для каждой строки из регистра выполняется соединение со справочником ХарактеристикиНоменклатуры.
  2. Если в базе нарушена ссылочная целостность или есть дублирующиеся записи (что редко, но возможно при специфических настройках), количество строк в выборке может измениться.
  3. Но даже без дублей, использование полей через точку в итоговых функциях заставляет платформу пересчитывать итоги по детальным записям, а не использовать агрегаты виртуальной таблицы.

Посмотрим на пример кода, который вызывает проблему:


ИсточникДанных.ВесНачальныйОстаток - ЕСТЬNULL(ИсточникДанных.ХарактеристикаНоменклатуры.ЮС_ВесВставок, 0) 
* ИсточникДанных.КоличествоНачальныйОстаток КАК ВесМеталлаНачальныйОстаток

Здесь мы не просто достаем данные, мы заставляем систему для каждой строки регистратора выполнять умножение и вычитание. Если в каких-то строках КоличествоНачальныйОстаток равно NULL (что бывает в виртуальных таблицах при определенных настройках), а ЕСТЬNULL не обработан для всех участников выражения, результат всей строки может стать неопределенным или измениться при суммировании.

Решение №1: Использование временных таблиц

Чтобы гарантировать стабильность остатков, мы должны разделить получение данных из регистра и расчет дополнительных показателей. Это лучший способ работы в 1С. Разберем по шагам, как переписать запрос.

Шаг 1. Получаем «чистые» данные из регистра во временную таблицу. На этом этапе мы не используем обращения через точку и не считаем вес вставок.


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

Шаг 2. Вторым запросом соединяем временную таблицу со справочником и выполняем вычисления. Теперь наши расчеты веса никак не повлияют на то, как платформа извлекла остатки из реальных таблиц БД.


ВЫБРАТЬ
    Т.Номенклатура,
    Т.Характеристика,
    Т.КоличествоНачальныйОстаток,
    // Теперь расчет безопасен
    Т.ВесНачальныйОстаток - ЕСТЬNULL(Т.Характеристика.ЮС_ВесВставок, 0) * Т.КоличествоНачальныйОстаток КАК ВесМеталлаНачальныйОстаток,
    Т.Регистратор
ИЗ
    ВТ_ДанныеРегистра КАК Т

Решение №2: Правильная настройка ролей полей

Если вы используете Построитель отчетов или систему компоновки данных (СКД), обратите внимание на фигурные скобки {} в вашем запросе. Они указывают на то, что это «расширение языка запросов» для компоновки. В таких случаях проблема может быть в ролях полей.

Проанализируем ситуацию: когда вы создаете новое поле ВесМеталлаНачальныйОстаток, система по умолчанию считает его обычным числом (ресурсом). Для остатков в 1С предусмотрена специальная роль. Если вы не укажете, что это поле является «Начальным остатком», система будет просто суммировать его значения при группировке, вместо того чтобы брать значение на начало периода.

Решение №3: Изменение логики получения общего итога

Вернемся к вопросу автора: «почему меняется значение итогов». Если нам нужно получить корректный общий остаток по складу на начало периода, нам нельзя использовать СУММА(НачальныйОстаток) по записям регистратора.

Рассмотрим правильный подход:

  1. Если нужен итог по всей таблице, используйте метод Остатки() в отдельном запросе для получения граничных значений.
  2. Или используйте СКД, которая умеет правильно рассчитывать остатки по группировкам (используя функции ВычислитьВыражениеАгрегат или просто правильно настроенные ресурсы).
  3. В обычном запросе с итогами по регистратору, чтобы получить правильный начальный остаток в иерархии, нужно использовать функцию МИНИМУМ или специфические механизмы платформы, но суммирование — это всегда ошибка.

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

← На главную