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