Одной из наиболее часто встречающихся задач при разработке отчетов в 1С является получение конечных остатков на каждую дату выбранного периода. Стандартная виртуальная таблица ОстаткиИОбороты при выборе периодичности День возвращает данные только за те дни, в которых были совершены какие-либо движения (приход или расход). Если движений не было, день просто выпадает из выборки, что недопустимо для полноценного анализа складских запасов или взаиморасчетов.
В этой статье мы подробно разберем, как заставить систему выводить остатки на каждый день, проанализируем типичные ошибки и рассмотрим несколько способов решения задачи: от простых настроек системы компоновки данных (СКД) до сложных запросов с использованием временных таблиц.
Прежде всего, выясним причину, по которой обычный запрос не выводит пустые дни. Виртуальная таблица РегистрНакопления.Товары.ОстаткиИОбороты оптимизирована для быстродействия. Она не хранит остатки на каждую секунду времени, а рассчитывает их исходя из итогов и таблицы движений. Если в конкретный день записей в физической таблице не было, виртуальной таблице «не за что зацепиться», чтобы выдать строку. Даже использование ЛЕВОЕ СОЕДИНЕНИЕ с таблицей дат часто не спасает, так как остаток на «пустой» день не будет равен нулю — он должен быть равен остатку на предыдущую дату.
Это самый простой и рекомендуемый способ, если вы используете Систему компоновки данных. Рассмотрим его по шагам.
Во-первых, в тексте запроса набора данных необходимо использовать периодичность Авто или День. Пример базового запроса:
ВЫБРАТЬ
Товары.Филиал КАК Филиал,
Товары.Номенклатура КАК Номенклатура,
Товары.ПериодДень КАК Период,
Товары.КоличествоКонечныйОстаток КАК КонечныйОстаток
ИЗ
РегистрНакопления.Товары.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Авто, , ) КАК Товары
Во-вторых, перейдем в настройки отчета (вкладка «Настройки» в СКД). Для группировки по полю Период нужно выполнить следующие действия:
Период.День (или другой нужный период).&НачалоПериода и &КонецПериода.После этих манипуляций СКД сама добавит недостающие даты в итоговый отчет.
Часто программисты сталкиваются с тем, что «Дополнение периода» включено, но данные все равно отображаются некорректно или остатки не «протягиваются» на пустые дни. Проанализируем ситуацию: чтобы СКД понимала, как рассчитывать остаток для добавленных дат, она должна четко знать, какое поле является периодом, а какое — остатком.
Разберем настройку ролей на вкладке «Набор данных»:
Период в колонке «Роль» должно быть указано: Период, порядковый номер (например, 1), тип — Дополнительный (или основной, если это единственный период).КонечныйОстаток в колонке «Роль» необходимо настроить категорию «Остатки». В поле «Имя» указывается общее имя (например, Количество), а в поле «Тип» выбирается КонечныйОстаток. Также нужно указать поле периода, к которому привязан этот остаток.Важный момент: если вы хотите видеть остатки по всем филиалам или складам, даже если по конкретному складу не было движения в определенный день, в роли поля остатка должны быть правильно указаны Измерения. Это позволит СКД «размножить» строки по комбинации Период + Склад.
Если задача стоит вне рамок СКД (например, нужно получить таблицу значений в коде), рассмотрим вариант с созданием временной таблицы всех дат периода. Посмотрим на пример кода, где мы генерируем периоды:
// Создадим таблицу всех дат месяца
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Номера.Номер - 1) КАК ДатаПериода
ПОМЕСТИТЬ ВТ_ВсеДаты
ИЗ
(ВЫБРАТЬ 1 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 2 ... ОБЪЕДИНИТЬ ВЫБРАТЬ 31) КАК Номера
ГДЕ
ДОБАВИТЬКДАТЕ(&НачалоПериода, ДЕНЬ, Номера.Номер - 1) <= &КонецПериода
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ВсеДаты.ДатаПериода КАК Период,
ЕСТЬNULL(Товары.КоличествоКонечныйОстаток, 0) КАК Остаток
ИЗ
ВТ_ВсеДаты КАК ВТ_ВсеДаты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Товары.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , ) КАК Товары
ПО ВТ_ВсеДаты.ДатаПериода = Товары.Период
Проанализируем этот запрос. Здесь кроется главная ловушка: ЕСТЬNULL(Товары.КоличествоКонечныйОстаток, 0) вернет 0 в те дни, когда движения не было. Но остаток-то на складе есть! Он просто не изменился. Для решения этой проблемы в «чистом» запросе (без СКД) обычно используют расчет накопленным итогом.
Этот метод считается наиболее ресурсоемким, но он гарантирует результат в любых условиях. Суть заключается в том, чтобы получить все движения, а затем для каждой даты просуммировать все изменения, которые произошли до этой даты включительно.
Рассмотрим логику построения такого запроса:
Движения.Период <= ВТ_ВсеДаты.ДатаПериода.ВТ_ВсеДаты.ДатаПериода и суммируем разницу приходов и расходов.Пример реализации части запроса:
ВЫБРАТЬ
ВТ_Даты.Период КАК Период,
СУММА(ВТ_Движения.КоличествоОборот) КАК КонечныйОстаток
ИЗ
ВТ_ВсеДаты КАК ВТ_Даты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Движения КАК ВТ_Движения
ПО ВТ_Движения.Период <= ВТ_Даты.Период
СГРУППИРОВАТЬ ПО
ВТ_Даты.Период
Выясним, на что стоит обратить внимание при реализации любого из выбранных методов:
РегистрСведений.Календарь или РегистрСведений.ГрафикиРаботы. Это надежнее, чем использование регистра КурсыВалют, так как курсы могут не обновляться в выходные и праздники.НАЧАЛОПЕРИОДА(Дата, ДЕНЬ). Это исключит ошибки, связанные с разным временем документов (например, документ в 12:00:00 не соединится с датой в 00:00:00).Авто позволяет системе самой определять нужные разрезы периодов, что часто работает эффективнее ручного указания периодичности День.ИТОГИ ... ПО ПЕРИОДУ(ДЕНЬ, &Начало, &Конец) работает при выгрузке в РезультатЗапроса, но полностью игнорируется СКД.Подводя итог, отметим: для отчетов на СКД самым правильным и быстрым решением является использование Ролей полей и Типа дополнения «День» в настройках группировки. Для сложных алгоритмов внутри обработок используйте временную таблицу дат и соединение по условию МЕНЬШЕ ИЛИ РАВНО для расчета актуального остатка на каждую секунду времени.