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

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

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

Суть проблемы и почему «Остатки» не подходят напрямую

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

Решение через виртуальную таблицу «ОстаткиИОбороты»

Самый правильный и производительный способ решения данной задачи заключается в использовании виртуальной таблицы ОстаткиИОбороты с установленной периодичностью. Давайте разберем этот алгоритм по шагам.

Шаг 1: Подготовка исходных данных

Для начала нам необходимо передать наш список дат в запрос. Лучше всего это сделать через ТаблицуЗначений, которую мы поместим во временную таблицу. Проанализируем пример подготовки данных в коде 1С:


// Предположим, СписокДат - это массив или колонка ТЗ с нужными датами
ТЗДаты = Новый ТаблицаЗначений;
ТЗДаты.Колонки.Добавить("ДатаОстатка", Новый ОписаниеТипов("Дата"));

Для Каждого ТекДата Из СписокДат Цикл
    НоваяСтрока = ТЗДаты.Добавить();
    НоваяСтрока.ДатаОстатка = КонецПериода(ТекДата, День);
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Даты ИЗ &ТЗДаты КАК ТЗДаты";
Запрос.УстановитьПараметр("ТЗДаты", ТЗДаты);

Шаг 2: Определение границ периода

Чтобы виртуальная таблица ОстаткиИОбороты работала максимально быстро, мы должны ограничить ее выборку минимальной и максимальной датой из нашего списка. Проанализируем, как это добавить в запрос:


ВЫБРАТЬ
    МИНИМУМ(ВТ_Даты.ДатаОстатка) КАК МинДата,
    МАКСИМУМ(ВТ_Даты.ДатаОстатка) КАК МаксДата
ПОМЕСТИТЬ ВТ_Границы
ИЗ
    ВТ_Даты КАК ВТ_Даты;

Шаг 3: Получение данных из регистра

Теперь мы обращаемся к таблице ОстаткиИОбороты. Важнейший момент здесь — параметр Периодичность. Мы установим его в значение Период. Это заставит систему выдать нам остатки на каждый момент времени, когда в регистре происходили изменения в рамках заданного интервала.


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

Шаг 4: Соединение дат и остатков

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

Рассмотрим финальную структуру запроса:


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

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

Альтернативный метод: Расчет от актуальных остатков

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

  1. Получаем текущие остатки на самую позднюю дату из списка (используя Остатки).
  2. Получаем обороты за весь период от самой ранней до самой поздней даты.
  3. Путем вычитания оборотов из конечного остатка вычисляем остатки на промежуточные даты.

Этот метод сложнее в реализации (нужно аккуратно работать с арифметикой «Приход/Расход»), но он может работать значительно быстрее, так как не требует сканирования итогов за все время существования базы.

Важные нюансы работы с временем и границами

При получении остатков в 1С критически важно понимать разницу между Дата и Граница. Выясним причину возможных расхождений данных:

Если вы запрашиваете остаток на '2023-12-31 23:59:59', документы, записанные в эту же секунду, могут не попасть в результат, если они расположены «после» момента времени запроса. В коде 1С рекомендуется использовать объект Граница(Дата, ВидГраницы.Включая). В самом же тексте запроса, при сравнении дат (как в нашем примере с <=), условие «меньше или равно» фактически имитирует поведение «включая секунду».

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

Проанализируем ситуацию, когда данных слишком много. Чтобы запрос не «повесил» систему, следует:

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

← На главную