← Назад к вопросам

Остаток товара по складам на каждый день

2.8 Senior🔥 221 комментариев
#Запросы и оптимизация#Регистры

Условие

Напишите запрос, который выводит остаток товара по складам на каждый день в заданном интервале, даже если в этот день не было движений.

Дано:

  • Справочники "Номенклатура" и "Склады"
  • Регистр накопления "ОстаткиТоваров" с измерениями "Номенклатура", "Склад" и ресурсом "Количество"

Пример

Интервал: 01.01.2024 - 03.01.2024

ДатаСкладНоменклатураОстаток
01.01.2024ОсновнойТовар 1100
02.01.2024ОсновнойТовар 1100
03.01.2024ОсновнойТовар 185

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

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

Способ 1: С использованием вспомогательной таблицы дат

ДатаНачала = '2024-01-01';
ДатаОкончания = '2024-01-03';

Запрос = Новый Запрос();
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Запрос.Текст = 
"""ВЫБРАТЬ
    Остатки.Дата,
    Остатки.Склад,
    Остатки.Номенклатура,
    Остатки.КоличествоОстаток КАК Остаток
ИЗ
    РегистрНакопления.ОстаткиТоваров.Остатки(
        @ДатаОкончания,
        Дата,
        Номенклатура,
        Склад
    ) КАК Остатки
ГДЕ
    Остатки.Дата >= @ДатаНачала
    И Остатки.Дата <= @ДатаОкончания
ПОРЯДОКПО
    Остатки.Дата,
    Остатки.Склад,
    Остатки.Номенклатура""";

Таблица = Запрос.Выполнить().Выгрузить();

Проблема: если в какой-то день не было движений по товару на складе, такая строка не выведется.

Способ 2: Через кросс-джойн (правильный подход)

ДатаНачала = '2024-01-01';
ДатаОкончания = '2024-01-03';

Запрос = Новый Запрос();
Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Запрос.Текст = 
"""ВЫБРАТЬ
    Даты.Дата,
    Склады.Ссылка КАК Склад,
    Номенклатура.Ссылка КАК Номенклатура,
    ВЫБОР
        КОГДА Остатки.Количество ЕСТЬ NULL
            ТОГДА 0
        ИНАЧЕ Остатки.Количество
    КОНЕЦ КАК Остаток
ИЗ
    Справочник.Склады КАК Склады,
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(
        @ДатаОкончания,
        Номенклатура,
        Склад
    ) КАК Остатки
    ПО Остатки.Номенклатура = Номенклатура.Ссылка
    И Остатки.Склад = Склады.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ РАЗЛИЧНЫЕ
            ДатаНач + РАЗНИЦА_ДНЕЙ * 86400 КАК Дата
        ИЗ
            (ВЫБРАТЬ РАЗЛИЧНЫЕ 0 КАК РАЗНИЦА_ДНЕЙ
             ОБЪЕДИНИТЬ
             ВЫБРАТЬ 1
             ОБЪЕДИНИТЬ
             ВЫБРАТЬ 2)
    ) КАК Даты
    ПО Остатки.Дата >= Даты.Дата
ГДЕ
    Даты.Дата >= @ДатаНачала
    И Даты.Дата <= @ДатаОкончания
ПОРЯДОКПО
    Даты.Дата,
    Склады.Ссылка,
    Номенклатура.Ссылка""";

Способ 3: Самый надёжный (через программный цикл)

ДатаНачала = '2024-01-01';
ДатаОкончания = '2024-01-03';

Результат = Новая ТаблицаЗначений();
Результат.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
Результат.Колонки.Добавить("Склад", Новый ОписаниеТипов("Справочник.Склады"));
Результат.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("Справочник.Номенклатура"));
Результат.Колонки.Добавить("Остаток", Новый ОписаниеТипов("Число"));

// Получаем все склады и номенклатуру
Запрос = Новый Запрос();
Запрос.Текст = 
"""ВЫБРАТЬ
    Склады.Ссылка КАК СкладСсылка,
    Номенклатура.Ссылка КАК НоменклатураСсылка
ИЗ
    Справочник.Склады КАС Склады,
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    НЕ Склады.ПометкаУдаления
    И НЕ Номенклатура.ПометкаУдаления""";

ТаблицаСкладыНоменклатура = Запрос.Выполнить().Выгрузить();

ТекущаяДата = ДатаНачала;
Пока ТекущаяДата <= ДатаОкончания Цикл
    Для Каждого Строка Из ТаблицаСкладыНоменклатура Цикл
        // Получаем остаток на эту дату
        Остаток = РегистрНакопления.ОстаткиТоваров.ПолучитьОстаток(
            ТекущаяДата,
            Новая Структура(
                "Номенклатура", Строка.НоменклатураСсылка,
                "Склад", Строка.СкладСсылка
            )
        );
        
        НоваяСтрока = Результат.Добавить();
        НоваяСтрока.Дата = ТекущаяДата;
        НоваяСтрока.Склад = Строка.СкладСсылка;
        НоваяСтрока.Номенклатура = Строка.НоменклатураСсылка;
        НоваяСтрока.Остаток = Остаток;
    КонецЦикла;
    
    ТекущаяДата = ТекущаяДата + 86400;
КонецЦикла;

Ключевые моменты решения:

Виртуальная таблица "Остатки":

  • Она автоматически считает остатки с учётом всех движений
  • Параметры: (ДатаКонца, Периодичность, Измерения)
  • Периодичность "Дата" показывает остаток на каждую дату

Кросс-джойн:

  • Используется декартово произведение всех комбинаций дат, складов, номенклатуры
  • ЛЕВОЕ СОЕДИНЕНИЕ гарантирует строки даже без движений (остаток = 0)
  • ВЫБОР КОГДА NULL ТОГДА 0 преобразует NULL в ноль

Программный цикл:

  • Самый простой и надёжный способ
  • Подходит для малых-средних объёмов данных
  • ПолучитьОстаток() вычисляет остаток эффективнее, чем виртуальная таблица

Рекомендация: Используй Способ 3 (программный цикл) — он наиболее понятен и стабилен.

Остаток товара по складам на каждый день | PrepBro