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