Почему при расчете остатков по месяцам в запросе 1С получаются неверные суммы и как это исправить?

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

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

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

Разберем классическую ситуацию. Допустим, на складе на начало января лежало 10 единиц товара. В январе, феврале и марте движений не было. Если мы сделаем запрос к виртуальной таблице ОстаткиИОбороты с периодичностью Месяц, мы получим три строки: за январь, февраль и март. В каждой строке начальный остаток будет равен 10.

Если мы попытаемся просто сложить эти данные (применить функцию СУММА в группировке или итогах), то получим:

  1. Начальный остаток: 10 (январь) + 10 (февраль) + 10 (март) = 30.
  2. Конечный остаток: 10 (январь) + 10 (февраль) + 10 (март) = 30.

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

Анализируем поведение временных таблиц

Рассмотрим ситуацию, описанную автором. При использовании вложенного запроса 1С еще может попытаться оптимизировать расчет итогов, но как только данные помещаются во временную таблицу, они превращаются в «плоский» набор чисел. Платформа больше не понимает, что число в колонке СтоимостьНО — это остаток на начало периода. Для СУБД это просто числовое поле (ресурс), которое можно суммировать.

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


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Данные.Период,
    ВТ_Данные.СтоимостьНО
ИЗ
    ВТ_Данные
ИТОГИ
    СУММА(СтоимостьНО)
ПО
    ОБЩИЕ

В данном случае, если товар лежал на складе 12 месяцев без движения, общий итог по СтоимостьНО будет в 12 раз больше реального остатка. Это происходит потому, что во временную таблицу записалось 12 строк с одинаковой суммой, и итоговый запрос их просто сложил.

Метод 1: Использование Системы компоновки данных (СКД)

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

  1. Перейдем на закладку «Поля» в схеме компоновки.
  2. Для полей начального и конечного остатка найдем колонку «Роль».
  3. Настроим роль для поля начального остатка: укажем тип Остаток, имя группы (например, «Остаток»), тип остатка — Начальный, и обязательно выберем поле периода (например, ПериодМесяц).
  4. Аналогично настроим конечный остаток, указав тип Конечный.

После такой настройки СКД перестанет тупо складывать остатки. Для группировки «Итого» она возьмет значение начального остатка из самой первой строки периода, а конечного — из самой последней. Проблема «раздувания» сумм будет решена автоматически.

Метод 2: Корректный расчет в обычном запросе

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

  1. Получаем начальный остаток на самую раннюю дату (&ДатаНачала).
  2. Получаем конечный остаток на самую позднюю дату (&ДатаОкончания).
  3. Получаем обороты в разрезе периодов (месяцев).
  4. Объединяем эти данные.

Пример структуры кода для получения корректных итогов без «двоения» сумм:


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

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

Особенности виртуальной таблицы ОстаткиИОбороты

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

Если в мае движений по товару не было, то при группировке по месяцам строка за май может просто отсутствовать в результате. Это приведет к «дырам» в отчете. Рассмотрим, как этого избежать:

Резюме

Подведем итог: использование временных таблиц для хранения остатков в разрезе периодов допустимо только в том случае, если вы не планируете в дальнейшем суммировать эти остатки по вертикали (делать общие итоги). Если же вам нужны корректные общие суммы за период, используйте СКД с правильно настроенными ролями полей — это избавит вас от необходимости писать сложные SQL-конструкции и вручную вычислять значения на границы дат.

← На главную