Как получить остатки и движения по всем дням в 1С (даже если не было движений)

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

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

Почему виртуальная таблица не возвращает пустые дни?

Рассмотрим механику работы виртуальной таблицы ОстаткиИОбороты. Платформа 1С оптимизирует запросы к базе данных и обращается к физическим таблицам накопления и таблицам итогов. Когда мы указываем периодичность День, система анализирует записи регистра. Если за 12-е число записей нет, то и вычислять остаток не для чего. Параметр МетодДополнения (например, ДвиженияИГраницыПериода) может добавить строки только на начало и конец всего выбранного интервала, но не заполнит «дыры» внутри него. Чтобы решить эту задачу, нам потребуется создать «сетку» из всех дат периода.

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

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

  1. Создание набора данных: Напишем простой запрос к виртуальной таблице ТоварыОрганизаций.ОстаткиИОбороты. В параметрах таблицы укажем периодичность День.
  2. Настройка ролей полей: В наборе данных запроса найдем поле Период. В колонке «Роль» необходимо указать, что это поле является периодом, и установить тип дополнения День. Также важно указать параметры начала и окончания периода, чтобы СКД знала границы «сетки».
  3. Настройки отчета: В разделе настроек (вкладка «Настройки» -> «Детальные записи» или группировка по Периоду) нужно убедиться, что для поля периода включен флаг Дополнять периоды.

При таком подходе СКД сама создаст строки для отсутствующих дат и «протянет» значение остатка с предыдущего дня. Это работает быстро и учитывает все отборы.

Решение №2: Формирование сетки дат в запросе

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

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


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

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


// Шаг 2: Создаем матрицу "Каждый товар - Каждый день"
ВЫБРАТЬ
    ВТ_Дни.Период КАК Период,
    ВТ_Номенклатура.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Матрица
ИЗ
    ВТ_Дни КАК ВТ_Дни
        СОЕДИНЕНИЕ ВТ_Номенклатура КАК ВТ_Номенклатура
        ПО (ИСТИНА) // Перекрестное соединение
;

После создания матрицы мы можем присоединить к ней данные о движениях и остатках. Рассмотрим пример итогового запроса:


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

Как заполнить остатки в «пустые» дни?

Запрос выше все еще имеет один изъян: если 13-го числа движений не было, поле Данные.КоличествоКонечныйОстаток будет содержать NULL (или 0 после ЕСТЬNULL), хотя фактически товар на складе лежал. Нам нужно получить «проходящий» остаток.

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

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

Проанализируем пример расчета через нарастающий итог (это более надежно для запросов):


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

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

Оптимизация и производительность

Рассмотрим ситуацию, когда данных очень много. Запрос в цикле (как предлагал автор в сообщении №10) — это худшее решение. Это создает огромную нагрузку на сервер СУБД и замедляет работу системы. Если вы все же решили использовать запрос с соединением:

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

← На главную