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

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

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

Анализ проблемы «пустых дней»

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

Для решения этой задачи мы воспользуемся проверенным методом: созданием временной таблицы всех дат за период и последующим соединением её с данными регистра. Разберем этот процесс по шагам.

Шаг 1: Генерация последовательности дат

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

Рассмотрим пример формирования таблицы дат:


ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Номера.Номер) КАК Период
ПОМЕСТИТЬ ВТ_Календарь
ИЗ
    (ВЫБРАТЬ
        (т1.Номер + т10.Номер * 10 + т100.Номер * 100) КАК Номер
    ИЗ
        (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК т10 ПО ИСТИНА
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВЫБРАТЬ 4 ОБЪЕДИНИТЬ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВЫБРАТЬ 9) КАК т100 ПО ИСТИНА
    ) КАК Номера
ГДЕ
    ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Номера.Номер) <= &КонецПериода
;
ИНДЕКСИРОВАТЬ ПО
    Период

В данном фрагменте кода мы создаем последовательность чисел от 0 до 999 и прибавляем их к начальной дате. Это позволяет получить таблицу всех дат периода. Обратите внимание на использование инструкции ИНДЕКСИРОВАТЬ ПО — это критически важно для последующего соединения таблиц, особенно если период большой.

Шаг 2: Получение движений и начальных остатков

Теперь проанализируем, как получить данные из регистра. Нам нужны остатки на начало периода и все обороты внутри периода. Для этого лучше всего подходит виртуальная таблица ОстаткиИОбороты.


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

Шаг 3: Соединение календаря с данными регистра

Выясним причину, по которой обычное соединение может не сработать. Если мы просто соединим даты с остатками, у нас все равно останутся «дырки» (NULL), так как остатки есть не на каждый день. Нам нужно использовать Левое соединение календаря с данными и последующую обработку итогов.

Рассмотрим пример итогового запроса с использованием механизма СОЕДИНЕНИЕ и функции ЕСТЬNULL:


ВЫБРАТЬ
    Календарь.Период КАК Период,
    Данные.Номенклатура КАК Номенклатура,
    Данные.КонечныйОстаток КАК КонечныйОстаток
ИЗ
    ВТ_Календарь КАК Календарь
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДанныеРегистра КАК Данные
        ПО Календарь.Период >= Данные.Период

Внимание! Такое соединение «всех со всеми» по условию >= может привести к огромному количеству строк в результате (декартово произведение), что крайне негативно скажется на производительности. Проанализируем более оптимальный путь.

Шаг 4: Использование СКД для заполнения периодов

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

  1. Добавьте набор данных - запрос, который выбирает только реальные движения из регистра.
  2. Перейдите на закладку «Настройки», выберите группировку по полю Период.
  3. В настройках группировки установите Тип дополненияДень.
  4. Укажите Начало периода и Конец периода.

В этом случае платформа сама сгенерирует недостающие строки и «протянет» остатки на пустые даты. Это избавляет программиста от написания сложных и медленных запросов вручную.

Шаг 5: Программное заполнение пропусков в коде (если не СКД)

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


ТекущийОстаток = 0;
Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл
    Если Выборка.КонечныйОстаток <> NULL Тогда
        ТекущийОстаток = Выборка.КонечныйОстаток;
    КонецЕсли;
    
    // Теперь переменная ТекущийОстаток содержит актуальное значение 
    // даже для тех дат, по которым не было движений
    Сообщить("Дата: " + Выборка.Период + " Остаток: " + ТекущийОстаток);
КонецЦикла;

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

Рассмотрим важные моменты, которые помогут ускорить работу системы:

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

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

← На главную