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