Отчеты и Аналитика · 13 мин
Как получить остатки на каждый день в запросе 1С
Как получить остатки на каждый день в запросе 1С: виртуальная таблица ОстаткиИОбороты, соединение с календарём, примеры кода и типичные ошибки.
Задача получить остатки на каждый день периода встречается при построении отчётов по складу, кассе, взаиморасчётам и в динамических графиках. Проблема в том, что регистр накопления хранит движения только на те даты, когда были документы: если за день не было ни одной операции, записи в таблице нет, и простой запрос по остаткам вернёт «дырявый» результат. Чтобы получить непрерывную линейку остатков, нужно объединить данные регистра с полным списком дат периода.
Существует два рабочих подхода. Первый — виртуальная таблица ОстаткиИОбороты с периодичностью «День»: она сама раскладывает остатки по дням, но только по тем дням, где есть движения. Второй — соединение таблицы дат (календаря) с остатками на конец каждого дня. Выбор зависит от того, нужны ли «пустые» дни в результате и насколько критична производительность.
Ниже разобраны оба способа с готовыми текстами запросов, условиями выбора, проверкой результата и типичными ошибками, которые приводят к неверным цифрам или медленной работе отчёта.
Как устроены остатки в регистрах 1С и почему дни «выпадают»
Регистр накопления вида «Остатки» физически хранит итоговые записи (таблицу итогов) с периодичностью месяц, а движения — в таблице записей. Когда вы обращаетесь к виртуальной таблице Остатки на конкретную дату, платформа рассчитывает значение на момент времени. Когда вы запрашиваете данные «по дням», платформа может вернуть строки только для дат, на которые есть движения, — день без документов в выборку не попадёт.
Это принципиально для отчётов вида «динамика остатков на складе»: если 5 марта не было ни прихода, ни расхода, остаток на 5 марта равен остатку на 4 марта, но запрос без специальных приёмов строку за 5 марта не покажет. Для графика или сводной таблицы такие пропуски критичны — линия «обрывается», а итоги по дням смещаются.
Поэтому корректная постановка задачи звучит так: получить остаток на конец (или начало) каждого календарного дня периода, включая дни без движений. Дальше рассмотрим, какими инструментами языка запросов это решается.
Способ 1: виртуальная таблица ОстаткиИОбороты с периодичностью «День»
Самый простой вариант — использовать виртуальную таблицу ОстаткиИОбороты и в параметрах указать периодичность «День». Тогда в результате появится поле Период, а остатки будут сгруппированы по дням. Подходит, когда дни без движений допустимо пропустить, например в аналитических отчётах, где важны только «активные» даты.
ВЫБРАТЬ
ОстаткиИОбороты.Период КАК День,
ОстаткиИОбороты.Номенклатура КАК Номенклатура,
ОстаткиИОбороты.КоличествоНачальныйОстаток КАК НачальныйОстаток,
ОстаткиИОбороты.КоличествоПриход КАК Приход,
ОстаткиИОбороты.КоличествоРасход КАК Расход,
ОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонечныйОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода,
День,
,
Склад = &Склад) КАК ОстаткиИОбороты
УПОРЯДОЧИТЬ ПО
День
Обратите внимание на порядок параметров виртуальной таблицы: начало периода, конец периода, периодичность, метод дополнения, условие. Если указать периодичность «День», но забыть про четвёртый параметр (метод дополнения периодов), дни без движений всё равно не появятся — именно этот параметр отвечает за дополнение результата пустыми периодами.
Чтобы платформа сама дополнила результат днями без движений, укажите метод дополнения ДвиженияИГраницыПериода. Тогда в выборке появятся все дни интервала, а для «пустых» дней остатки будут повторять значение предыдущего дня, а обороты будут нулевыми.
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода,
&КонецПериода,
День,
ДвиженияИГраницыПериода,
Склад = &Склад)
Способ 2: таблица дат и остатки на конец каждого дня
Когда метод дополнения недоступен (например, запрос сложный, с несколькими регистрами) или нужен полный контроль над расчётом, используют классический приём: формируют таблицу всех дат периода и для каждой даты получают остаток. Источник дат — регистр сведений «Регламентированный производственный календарь», любой документ с достаточным числом записей или заранее подготовленная временная таблица.
Надёжный вариант без обращения к календарю — сгенерировать даты запросом через соединение таблицы «чисел». Ниже пример с производственным календарём, который есть в большинстве типовых конфигураций:
ВЫБРАТЬ
Календарь.ДатаКалендаря КАК День
ПОМЕСТИТЬ ВТ_Дни
ИЗ
РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК Календарь
ГДЕ
Календарь.ДатаКалендаря МЕЖДУ &НачалоПериода И &КонецПериода
;
ВЫБРАТЬ
Дни.День КАК День,
Остатки.Номенклатура КАК Номенклатура,
ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
ВТ_Дни КАК Дни
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
ОстаткиИОбороты.Период КАК Период,
ОстаткиИОбороты.Номенклатура КАК Номенклатура,
ОстаткиИОбороты.КоличествоКонечныйОстаток КАК КоличествоОстаток
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(
&НачалоПериода, &КонецПериода, День, , ) КАК ОстаткиИОбороты
) КАК Остатки
ПО Дни.День = Остатки.Период
УПОРЯДОЧИТЬ ПО
День
Здесь внутренний запрос даёт остатки по дням с движениями, а левое соединение с таблицей дат «размазывает» результат по всему календарю. Функция ЕСТЬNULL подменяет NULL нулём там, где соединения не нашлось. Учтите ограничение: если в день не было движений, но остаток был ненулевой, этот запрос покажет 0, а не остаток предыдущего дня. Для корректного «переноса» остатка используйте метод дополнения из первого способа либо соединяйте каждый день с виртуальной таблицей Остатки на конец этого дня.
Вариант с прямым обращением к Остатки на каждую дату точнее, но заметно тяжелее: платформа выполняет расчёт итогов на каждый день отдельно. На длинных периодах и крупных регистрах такой запрос может работать минутами. Поэтому его применяют для коротких периодов или когда нужен остаток на строго определённый момент времени каждого дня.
Сравнение подходов
Быстро и просто. Подходит для отчётов по активным дням; дни без движений пропускаются без метода дополнения.
Полный календарь дней с корректным переносом остатков. Ограничение — работает только в рамках одной виртуальной таблицы.
Полный контроль и гибкость для сложных отчётов. Требует аккуратной обработки NULL и внимания к производительности.
Расчёт остатка нарастающим итогом через обороты
Третий приём полезен, когда виртуальные таблицы недоступны (например, данные собираются из нескольких источников): сначала получают обороты по дням, затем остаток на начало периода, и в коде или в СКД вычисляют остаток нарастающим итогом: ОстатокДня = ОстатокПредыдущегоДня + Приход - Расход.
В чистом запросе нарастающий итог делают самосоединением таблицы оборотов по условию «дата меньше или равна текущей» с группировкой — но такой запрос имеет квадратичную сложность и на больших объёмах работает медленно. Практичнее выгрузить обороты в таблицу значений и посчитать накопление в цикле на встроенном языке, либо использовать вычисляемые поля СКД с функцией ВычислитьВыражение.
- 1Определите, нужны ли дни без движений — от этого зависит выбор способа.
- 2Напишите запрос к ОстаткиИОбороты с периодичностью День и методом дополнения ДвиженияИГраницыПериода.
- 3Передайте границы периода корректно: начало дня и конец дня (или объект Граница).
- 4Добавьте отборы по измерениям (склад, организация) в параметры виртуальной таблицы, а не в секцию ГДЕ.
- 5Выполните запрос в консоли и сверьте остаток на последний день со стандартным отчётом по регистру.
- 6Проверьте пару дней без движений: остаток должен равняться остатку предыдущего дня.
Проверка результата и контрольные точки
Главный критерий правильности — сходимость с типовым отчётом. Откройте стандартный отчёт по регистру (например, «Остатки товаров на складах») на конец периода и сравните с конечным остатком вашего запроса. Цифры должны совпасть до копейки или до единицы измерения.
Вторая проверка — непрерывность: количество строк по одному объекту должно равняться числу дней в периоде. Если строк меньше, метод дополнения не сработал или таблица дат неполная. Третья проверка — логика переноса: возьмите день без движений и убедитесь, что его конечный остаток равен конечному остатку предыдущего дня.
Как проверить результат
Совпадает с типовым отчётом по регистру
Расхождение — проверяйте границы периода и отборы
Равно числу дней периода
Строк меньше — дни без движений потеряны
Остаток равен остатку предыдущего дня, обороты нулевые
Остаток обнулился или строка отсутствует
Начальный остаток равен остатку на конец предыдущего дня
Начальный остаток нулевой при наличии истории
Типичные ошибки и ограничения
Самая частая ошибка — передача в параметр конца периода даты без времени. Значение 31.03.0001 00:00:00 как конец периода отсекает все движения 31 марта, и остаток на последний день оказывается заниженным. Всегда используйте КонецДня() в коде или тип Граница с видом «Включая».
Вторая ошибка — наложение отборов через секцию ГДЕ вместо параметров виртуальной таблицы. Запрос сработает, но платформа сначала рассчитает итоги по всем измерениям, а потом отфильтрует результат — на больших регистрах это кратно увеличивает время выполнения. Отборы по измерениям всегда передавайте в последний параметр виртуальной таблицы.
Третья группа проблем — соединение по дате без учёта времени. Поле Период у виртуальной таблицы с периодичностью «День» содержит начало дня, поэтому соединять его с календарём нужно по равенству начал дней, а не по произвольным моментам времени. Также помните, что итоги регистра могут быть отключены или рассчитаны не на весь период — тогда виртуальные таблицы вернут неактуальные данные, и потребуется пересчёт итогов.
Производительность и оптимизация
Периодичность «День» на длинных интервалах порождает большое число строк: год — это 365 строк на каждую комбинацию измерений. Если в регистре тысячи номенклатурных позиций и десятки складов, результат разрастается до сотен тысяч записей. Сокращайте выборку отборами в параметрах виртуальной таблицы и не запрашивайте лишние измерения.
Дополнительные приёмы: помещайте промежуточные результаты во временные таблицы и создавайте на них индексы (ИНДЕКСИРОВАТЬ ПО) перед соединениями; в СКД используйте группировки вместо детальных записей, если детализация по номенклатуре не нужна; для регулярных отчётов рассмотрите предварительный расчёт данных фоновым заданием в нерабочее время.
Если отчёт строится на СКД, поле Период виртуальной таблицы удобно вывести как отдельную группировку-колонку: получится шахматка «объект × день» без дополнительного кода. Для графиков достаточно группировки-строки по дню и ресурса КонечныйОстаток.
Частые вопросы
Почему в результате запроса нет дней, когда не было документов?
Платформа возвращает строки только для периодов, в которых есть движения. Чтобы появились все дни, укажите в параметрах виртуальной таблицы ОстаткиИОбороты метод дополнения ДвиженияИГраницыПериода либо соедините результат с заранее сформированной таблицей дат периода.
Как получить остаток на начало дня, а не на конец?
Используйте поле НачальныйОстаток виртуальной таблицы ОстаткиИОбороты с периодичностью «День» — это и есть остаток на начало соответствующего дня. Альтернатива — обращение к таблице Остатки с периодом, равным началу нужного дня.
Запрос работает очень медленно, что делать в первую очередь?
Перенесите все отборы по измерениям из секции ГДЕ в параметры виртуальной таблицы, сократите период и уберите неиспользуемые измерения. Если этого мало — разбейте запрос на временные таблицы с индексами или считайте данные фоновым заданием в нерабочее время.
Можно ли получить остатки по дням для регистра бухгалтерии?
Да, у регистров бухгалтерии есть аналогичная виртуальная таблица ОстаткиИОбороты с параметрами периодичности и дополнения. Дополнительно укажите счёт и субконто в параметрах — это обязательное условие корректного и быстрого расчёта.
Остаток на день показывает ноль, хотя товар на складе есть. В чём причина?
Чаще всего в этот день не было движений, а запрос с левым соединением подставил ЕСТЬNULL(..., 0) вместо переноса остатка с предыдущего дня. Используйте метод дополнения ДвиженияИГраницыПериода или рассчитывайте остаток нарастающим итогом. Реже причина — нерассчитанные итоги регистра.
Чем отличается передача даты от объекта Граница в параметры виртуальной таблицы?
Дата без времени трактуется как начало дня, и движения последнего дня могут не попасть в расчёт. Объект Граница с видом «Включая» однозначно задаёт момент «по указанное время включительно», что исключает потерю документов на границе периода.
Что важно запомнить
Остатки на каждый день получают через ОстаткиИОбороты с периодичностью «День» и методом дополнения ДвиженияИГраницыПериода либо через соединение таблицы дат с остатками. Передавайте границы периода с учётом времени, отборы — в параметры виртуальной таблицы, а результат обязательно сверяйте с типовым отчётом по регистру.