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