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