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

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

В практике разработчика 1С часто возникает задача получения остатков регистров накопления не на фиксированную дату и не за простой линейный период, а на определенный, динамически формируемый список дат. Это могут быть даты проведения документов, даты изменения статусов объектов или, как в примере автора темы, даты из регистра сведений РегистрЗаписиИзменений.

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

---

Анализ исходного подхода и его узких мест

Рассмотрим алгоритм, предложенный в первом сообщении темы. Автор пытается получить остатки, используя декартово произведение (соединение без условий) таблицы дат и виртуальной таблицы ОстаткиИОбороты:


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

В этом коде есть несколько критических проблем:

  1. Декартово произведение (cross join). Строка ИЗ Дни КАК Дни, РегистрНакопления... КАК Движения заставляет СУБД перемножать каждую запись из таблицы дат на каждую запись таблицы остатков и оборотов. Если у вас 100 товаров и 30 дат, СУБД обработает 3000 строк. Но если товаров 10 000, а дат — 100, количество обрабатываемых строк вырастет до 1 000 000. Это вызовет колоссальную нагрузку на СУБД и зависание системы.
  2. Отсутствие жестких параметров периода. В реальной задаче у нас часто нет готовых параметров &НачалоПериода и &КонецПериода. Есть только набор дат во временной таблице, на которые нужно получить срез остатков.
  3. Неоптимальное условие связи. Условие Движения.Период <= Дни.Период находится внутри агрегатной функции СУММА(ВЫБОР...), что мешает СУБД использовать индексы и заставляет выполнять сканирование всей выборки.

---

Решение №1: Классический оптимизированный запрос с динамическим периодом

Выясним, как исправить исходный запрос. В первую очередь нам необходимо определить границы периода (минимальную и максимальную даты) прямо из нашей временной таблицы, чтобы передать их в параметры виртуальной таблицы ОстаткиИОбороты. Это позволит СУБД не рассчитывать итоги за всю историю регистра.

Разберем рабочий и оптимизированный вариант по шагам:

Шаг 1. Получаем список нужных дат и определяем границы периода.


ВЫБРАТЬ
    РегистрЗаписиИзменений.ДатаЗаписи КАК ДатаЗаписи
ПОМЕСТИТЬ втДни
ИЗ
    РегистрСведений.РегистрЗаписиИзменений КАК РегистрЗаписиИзменений
ГДЕ
    РегистрЗаписиИзменений.Объект = &Объект
;

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

Шаг 2. Получаем остатки и обороты, ограничивая их выбранными границами.

Чтобы передать динамические границы в параметры виртуальной таблицы, мы можем использовать соединение с таблицей втГраницыПериода или рассчитать их предварительно в коде. Ниже представлен вариант с получением данных через левое соединение, где условие даты перенесено в связь таблиц (как советовали в Сообщении 5):


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

СГРУППИРОВАТЬ ПО
    Движения.Номенклатура,
    Дни.ДатаЗаписи

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

---

Решение №2: Расчет остатков напрямую по физической таблице движений

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

Рассмотрим пример запроса, рассчитывающего остаток на каждую дату нарастающим итогом через таблицу движений:


ВЫБРАТЬ
    Дни.ДатаЗаписи КАК Период,
    Движения.Номенклатура КАК Номенклатура,
    СУММА(ВЫБОР 
        КОГДА Движения.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) 
            ТОГДА Движения.Количество 
        ИНАЧЕ -Движения.Количество 
    КОНЕЦ) КАК Остаток
ИЗ
    втДни КАК Дни
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах КАК Движения
        ПО Движения.Период <= Дни.ДатаЗаписи
СГРУППИРОВАТЬ ПО
    Дни.ДатаЗаписи,
    Движения.Номенклатура

Преимущества этого метода:

---

Решение №3: Получение остатков через Систему компоновки данных (СКД)

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

Проанализируем пошагово, как настроить такую схему:

  1. Создайте Набор данных - запрос (назовем его НаборДанныхДаты), который выбирает список ваших дат и объектов (например, Номенклатуру):
    
    ВЫБРАТЬ
        РегистрЗаписиИзменений.ДатаЗаписи КАК Период,
        РегистрЗаписиИзменений.Номенклатура КАК Номенклатура
    ИЗ
        РегистрСведений.РегистрЗаписиИзменений КАК РегистрЗаписиИзменений
    
  2. Создайте второй Набор данных - запрос (назовем его НаборДанныхОстатки), который получает остатки на одну конкретную дату:
    
    ВЫБРАТЬ
        Товары.Номенклатура КАК Номенклатура,
        Товары.КоличествоОстаток КАК КоличествоОстаток
    ИЗ
        РегистрНакопления.ТоварыНаСкладах.Остатки(&ПериодГраница, Номенклатура = &Номенклатура) КАК Товары
    
  3. Перейдите на вкладку Связи наборов данных и добавьте новую связь:
    • Источник связи: НаборДанныхДаты
    • Приемник связи: НаборДанныхОстатки
    • Условие связи: Номенклатура = Номенклатура
    • Параметры: Имя параметра — ПериодГраница, Выражение источника — Период.

При таком подходе платформа 1С автоматически оптимизирует выполнение запросов на уровне СУБД. Она сформирует пакетные запросы к остаткам конкретно под каждую дату из вашей таблицы, исключая избыточные расчеты оборотов.

---

Рекомендации по оптимизации

При работе с остатками на список дат всегда придерживайтесь следующих правил:

← На главную