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

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

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

Причина проблемы: начало дня против конца дня

Проанализируем ситуацию, с которой столкнулся автор темы. При получении остатков на каждый день часто возникает путаница с временными границами. В 1С дата без указания времени (например, 01.01.2023) технически является началом дня (01.01.2023 00:00:00). Если мы хотим получить остаток, актуальный на конец суток, нам необходимо явно указывать время 23:59:59. В противном случае движения, совершенные в течение дня, не попадут в расчет остатка на эту дату.

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

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

Рассмотрим пример кода для генерации таблицы дат с использованием функции ДОБАВИТЬКДАТЕ:


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

Важный момент: использование функции КОНЕЦПЕРИОДА гарантирует, что каждая дата в нашей таблице будет установлена на последнюю секунду дня (23:59:59). Это критически важно для корректного соединения с таблицей остатков.

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

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


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

Шаг 3: Соединение дат с остатками (Метод «протягивания»)

Это самый сложный этап. Нам нужно для каждой даты из ТаблицаДат найти «самый свежий» доступный остаток из таблицы РеальныеОстатки. Выясним причину, по которой обычное левое соединение здесь не сработает: если 5-го числа движений не было, а 4-го были, то простое соединение по равенству дат оставит 5-е число с пустым (NULL) значением.

Для решения применим соединение по условию РеальныеОстатки.Период <= ТаблицаДат.Дата с последующей группировкой. Мы найдем максимальную дату периода, которая меньше или равна текущей дате из таблицы дат, и возьмем остаток именно на ту дату.

Посмотрим на пример реализации этой логики:


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

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

Альтернативный профессиональный подход: СКД

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

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

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

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

Проанализируем ситуацию с нагрузкой на сервер. Запросы, использующие соединение <=, имеют квадратичную сложность и могут выполняться очень долго на больших справочниках номенклатуры. Чтобы ускорить работу, придерживаемся следующих правил:

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

← На главную