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