В процессе разработки часто возникает задача получить остатки по бухгалтерским счетам на определенную дату с помощью запроса. Однако иногда можно столкнуться с парадоксальной ситуацией: стандартный отчет, например, универсальная оборотно-сальдовая ведомость, показывает нулевые остатки, а прямой запрос к виртуальной таблице регистра возвращает ненулевую сумму. Давайте разберем, почему так происходит и как исправить эту проблему.
Проанализируем типичный случай. Чтобы обеспечить быстрый анализ данных, разработчик создает запрос для получения остатков по складу на определенную дату:
ВЫБРАТЬ
ХозрасчетныйОстатки.Счет КАК Счет,
ХозрасчетныйОстатки.Субконто2 КАК Склад,
ХозрасчетныйОстатки.КоличествоОстатокДт КАК КоличествоОстаток,
СУММА(ХозрасчетныйОстатки.СуммаОстатокДт) КАК СуммаОстаток
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет В ИЕРАРХИИ (&ВыбСчет), , Субконто2 = &Склад) КАК ХозрасчетныйОстатки
СГРУППИРОВАТЬ ПО
ХозрасчетныйОстатки.Счет,
ХозрасчетныйОстатки.КоличествоОстатокДт,
ХозрасчетныйОстатки.Субконто2
В результате выполнения запроса поле КоличествоОстаток равно нулю, а СуммаОстаток содержит какое-то значение. При этом ОСВ по этому же счету и складу на ту же дату показывает нули и по количеству, и по сумме. Выясним причину такого поведения системы.
Основная причина такого расхождения кроется в механизме хранения итогов платформой 1С. Для ускорения формирования отчетов система не вычисляет каждый раз данные по всем движениям регистра с самого начала времен. Вместо этого она хранит заранее рассчитанные итоги на определенные периоды (как правило, на начало каждого месяца) в специальных таблицах.
Когда мы обращаемся к разным объектам для получения данных, они могут использовать разные источники:
Остатки. Эта таблица предназначена для получения среза данных на один момент времени. Для оптимизации она часто обращается напрямую к таблице с заранее рассчитанными итогами. Если на дату запроса нет готового итога, она берет ближайший и рассчитывает остаток на основании движений. Но если "опорный" итог в таблице некорректен, результат запроса также будет неверным.ОстаткиИОбороты. На этой таблице строятся большинство стандартных отчетов, включая ОСВ. Она получает начальный остаток на начало периода, все обороты за период и вычисляет конечный остаток по формуле: КонечныйОстаток = НачальныйОстаток + Обороты. Этот механизм позволяет "самоисцеляться": даже если начальный остаток был сохранен с ошибкой, корректные обороты за период приводят к правильному конечному остатку в отчете.Таким образом, наша ситуация объясняется просто: в базе данных нарушилась целостность итогов. Запрос к таблице Остатки прочитал некорректный, "зависший" суммовой остаток из таблицы итогов, в то время как ОСВ, используя ОстаткиИОбороты, проанализировала обороты и правильно вычислила, что конечный остаток должен быть нулевым.
Когда могут "сломаться" итоги?
Основное и самое правильное решение в такой ситуации — принудительно заставить систему пересчитать и перезаписать итоги на основе таблицы движений. Это синхронизирует данные и исправит расхождения. Также перед пересчетом рекомендуется выполнить сравнение проводок по хозрасчетному регистру с суммой документа, чтобы убедиться в отсутствии логических ошибок в самих записях.
Разберем по шагам, как это сделать:
После завершения этой процедуры таблицы итогов будут полностью соответствовать таблицам движений. Если вы снова выполните свой первоначальный запрос, он вернет корректные нулевые остатки, как и ОСВ.
Хотя основная проблема была в данных, а не в коде, исходный запрос также содержит неточности, которые стоит исправить.
Посмотрим еще раз на секцию группировки:
СГРУППИРОВАТЬ ПО
ХозрасчетныйОстатки.Счет,
ХозрасчетныйОстатки.КоличествоОстатокДт,
ХозрасчетныйОстатки.Субконто2
В чем здесь ошибка?
Группировка должна производиться по измерениям или аналитическим разрезам (Счет, Субконто2), а не по ресурсам или результатам расчета (КоличествоОстатокДт). Включение ресурсного поля в группировку не имеет логического смысла и может приводить к неверным результатам или просто замедлять запрос.
Более того, виртуальная таблица Остатки уже возвращает сгруппированные данные по всем указанным измерениям (счет и виды субконто). Это значит, что дополнительная группировка и использование агрегатной функции СУММА() в данном случае избыточны.
Правильный и оптимизированный вариант запроса будет выглядеть так:
ВЫБРАТЬ
ХозрасчетныйОстатки.Счет КАК Счет,
ХозрасчетныйОстатки.Субконто2 КАК Склад,
ХозрасчетныйОстатки.КоличествоОстатокДт КАК КоличествоОстаток,
ХозрасчетныйОстатки.СуммаОстатокДт КАК СуммаОстаток
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, Счет В ИЕРАРХИИ (&ВыбСчет), , Субконто2 = &Склад) КАК ХозрасчетныйОстатки
Этот запрос не содержит секции СГРУППИРОВАТЬ ПО и агрегатных функций, так как в них нет необходимости. Он проще, читабельнее и будет выполняться немного быстрее.
При работе с остатками крайне важна точность указания периода. Если передать в параметр &Период дату без времени (например, '2025-07-08'), система по умолчанию возьмет начало дня ('2025-07-08 00:00:00'). Это значит, что все документы, проведенные в этот день, не попадут в расчет остатков.
Чтобы получить остатки на конец дня или на момент времени документа, необходимо использовать специальный объект Граница.
Пример получения остатков на конец дня:
&НаКонецДня
// В коде перед запросом:
Запрос.УстановитьПараметр("Период", Новый Граница(&ДатаОстатков, ВидГраницы.Включая));
// В тексте запроса:
...
ИЗ
РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, ...
...
Использование ВидГраницы.Включая гарантирует, что в расчет попадут все движения, выполненные до указанного момента времени включительно, что позволяет избежать ошибок при анализе данных "день в день".