Как рассчитать начальные остатки на каждую дату движения в запросе 1С?

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

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

В данной статье мы подробно разберем, как соединить таблицы остатков и оборотов, реализуем механизм «нарастающего итога» и настроим систему компоновки данных (СКД) для автоматического вычисления ресурсов по периодам.

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

Рассмотрим типовую ситуацию: у нас есть начальный остаток на фиксированную дату (например, начало месяца) и массив оборотов за последующий период. Нам нужно получить таблицу, где для каждой даты движения будет указан верный начальный остаток. Трудность заключается в том, что для второй и последующих дат начальный остаток должен включать в себя сумму начального остатка на 01.04 и всех предыдущих оборотов. Обычное левое соединение таблиц по периоду не решит задачу «протягивания» остатка на даты, где не было прихода или расхода именно в эту секунду.

Шаг 1. Подготовка временных таблиц с данными

Первым делом нам необходимо подготовить данные. Мы поместим начальные остатки и обороты в одну временную таблицу, чтобы в дальнейшем было удобно работать с единым набором данных. Для этого используем конструкцию ОБЪЕДИНИТЬ ВСЕ.

Проанализируем пример формирования такой таблицы:


// Помещаем начальные остатки во временную таблицу
ВЫБРАТЬ
    &НачалоПериода КАК Период,
    ТаблицаОстатков.Товар КАК Товар,
    ТаблицаОстатков.Остаток КАК НачОст,
    0 КАК Приход,
    0 КАК Расход
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    &ТаблицаОстатков КАК ТаблицаОстатков

ОБЪЕДИНИТЬ ВСЕ

// Добавляем обороты из другой таблицы
ВЫБРАТЬ
    ТаблицаОборотов.Период,
    ТаблицаОборотов.Товар,
    0,
    ТаблицаОборотов.Приход,
    ТаблицаОборотов.Расход
ИЗ
    &ТаблицаОборотов КАК ТаблицаОборотов
;

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

Шаг 2. Группировка и индексация

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


ВЫБРАТЬ
    ВТ_Данные.Период КАК Период,
    ВТ_Данные.Товар КАК Товар,
    СУММА(ВТ_Данные.НачОст) КАК НачОст,
    СУММА(ВТ_Данные.Приход) КАК Приход,
    СУММА(ВТ_Данные.Расход) КАК Расход
ПОМЕСТИТЬ ВТ_СгруппированныеДанные
ИЗ
    ВТ_Данные КАК ВТ_Данные
СГРУППИРОВАТЬ ПО
    ВТ_Данные.Период,
    ВТ_Данные.Товар
ИНДЕКСИРОВАТЬ ПО
    Товар,
    Период
;

Шаг 3. Реализация нарастающего итога в запросе

Чтобы получить начальный остаток на каждую дату, воспользуемся методом самоединения. Идея заключается в том, что для каждой записи из таблицы Т1 мы найдем все записи из таблицы Т2, период которых меньше периода текущей записи Т1. Сумма всех приходов и расходов из этих «прошлых» записей, добавленная к самому первому остатку, и даст нам начальный остаток на текущую дату.

Рассмотрим структуру запроса:


ВЫБРАТЬ
    Т1.Период КАК Период,
    Т1.Товар КАК Товар,
    // Нарастающий итог: сумма всех предыдущих оборотов + входящий остаток
    СУММА(Т2.НачОст + Т2.Приход - Т2.Расход) КАК НачальныйОстатокРасчетный,
    Т1.Приход КАК Приход,
    Т1.Расход КАК Расход
ИЗ
    ВТ_СгруппированныеДанные КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СгруппированныеДанные КАК Т2
        ПО Т1.Товар = Т2.Товар
            И Т1.Период > Т2.Период
СГРУППИРОВАТЬ ПО
    Т1.Период,
    Т1.Товар,
    Т1.Приход,
    Т1.Расход

Важный нюанс: условие Т1.Период > Т2.Период позволяет рассчитать остаток именно на начало периода Т1. Если использовать условие Т1.Период >= Т2.Период, мы получим конечный остаток на дату Т1.

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

Шаг 4. Использование ролей полей в СКД (Оптимальный способ)

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

Выясним, как это настроить:

  1. В запросе просто объедините начальные остатки (на самую раннюю дату) и обороты по датам в одну таблицу.
  2. Перейдите на закладку Наборы данных в конструкторе СКД.
  3. Для поля Остаток (которое содержит входящее сальдо) в колонке Роль нажмите на кнопку редактирования.
  4. Укажите тип Остатки. Выберите Вид остатка: Начальный.
  5. Укажите поле Период в качестве периода и поле Товар в качестве счета/аналитики.
  6. Аналогично можно настроить роль для конечного остатка.

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

Шаг 5. Работа с пропусками периодов

Проанализируем ситуацию, когда нам нужны остатки на каждый день, даже если движений не было. Для этого нам понадобится «таблица календаря» или «таблица периодов». Мы можем соединить таблицу всех дат периода с нашей таблицей оборотов левым соединением. Это гарантирует, что в результирующей таблице не будет «дырок» в датах, и остаток будет красиво дублироваться с предыдущего дня на следующий.

Пример логики вычисления остатка через агрегатную функцию СУММА с условием ВЫБОР (без самосоединения):


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

Здесь ДНИ.Период — это дата из таблицы-календаря, а Движения — наши данные. Этот подход часто используется в сложных бухгалтерских отчетах.

Заключение

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

← На главную