Как динамически изменять текст запроса в СКД в зависимости от параметров и метаданных?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке универсальных отчетов на Системе компоновки данных (СКД) разработчики часто сталкиваются с задачей поддержки единого отчета для типовых и доработанных конфигураций. Например, если в базе есть дополнительный нетиповой регистр сведений, отчет должен соединяться с ним и выводить расширенные данные. Если же конфигурация типовая и регистра в ней нет, отчет должен продолжать работать без ошибок. Разберем, почему стандартный язык запросов не позволяет решить эту задачу «в лоб», и проанализируем практические способы реализации такой логики.

Особенности валидации метаданных в СКД

Проанализируем природу проблемы. Если в текст запроса схемы СКД жестко прописать обращение к таблице, которой физически нет в конфигурации клиента, отчет завершится критической ошибкой «Таблица не найдена» еще на этапе инициализации схемы компоновки данных. Конструкции языка запросов вида ВЫБОР КОГДА здесь не помогут, так как синтаксический анализатор платформы 1С проверяет наличие всех указанных объектов метаданных до выполнения логики условий.

Расширения языка запросов СКД (конструкции в фигурных скобках {ГДЕ ...}, {ВЫБРАТЬ ...}) могут оптимизировать итоговый SQL-запрос, исключая неиспользуемые таблицы, однако они также требуют обязательного физического присутствия всех объектов в дереве метаданных конфигурации.

Рассмотрим основные способы решения этой проблемы.

Способ 1. Программная подмена запроса в событии ПриКомпоновкеРезультата

Разберем наиболее надежный и гибкий подход. В основном макете СКД отчет проектируется исключительно под типовые метаданные (базовый запрос). Это гарантирует, что отчет откроется и сформируется в любой базе без синтаксических ошибок. Если же в базе присутствует доработка и пользователь включил соответствующий параметр, мы программно подменяем текст запроса в схеме перед выполнением компоновки.

Посмотрим на пример реализации в модуле объекта отчета:


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

Способ 2. Использование набора данных «Объект» (внешняя таблица значений)

Рассмотрим вариант, при котором запрос вообще не хранится внутри макета СКД. В схеме создается набор данных типа Объект с описанием полей и их типов, а сам запрос выполняется в коде модуля через стандартный объект Запрос.

Разберем по шагам, как это работает:

  1. В коде модуля собираем текст запроса из фрагментов с помощью условий Если ... Тогда.
  2. Выполняем запрос и выгружаем результат в ТаблицаЗначений.
  3. Передаем полученную таблицу в процессор компоновки через структуру внешних наборов данных.

Посмотрим на пример кода:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
	
	СтандартнаяОбработка = Ложь;
	
	Запрос = Новый Запрос;
	ТекстЗапроса = 
		"ВЫБРАТЬ
		|	Остатки.Номенклатура КАК Номенклатура,
		|	Остатки.КоличествоОстаток КАК Количество";
	
	// Проверяем метаданные и добавляем поля
	Если Метаданные.РегистрыСведений.Найти("ДополнительныеСведенияОстатков") <> Неопределено Тогда
		ТекстЗапроса = ТекстЗапроса + ", ЕСТЬNULL(Доп.Коэффициент, 1) КАК Коэффициент ";
		ТекстИсточника = " ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Остатки
		| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДополнительныеСведенияОстатков КАК Доп 
		| ПО Остатки.Номенклатура = Доп.Номенклатура";
	Иначе
		ТекстЗапроса = ТекстЗапроса + ", 1 КАК Коэффициент ";
		ТекстИсточника = " ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК Остатки";
	КонецЕсли;
	
	Запрос.Текст = ТекстЗапроса + ТекстИсточника;
	Запрос.УстановитьПараметр("Период", ТекущаяДата());
	ТаблицаДанных = Запрос.Выполнить().Выгрузить();
	
	ВнешниеНаборыДанных = Новый Структура;
	ВнешниеНаборыДанных.Вставить("ТаблицаДанных", ТаблицаДанных);
	
	Схема = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
	Настройки = КомпоновщикНастроек.ПолучитьНастройки();
	
	КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
	МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки, ДанныеРасшифровки);
	
	ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
	ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
	
	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
	ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
	ПроцессорВывода.Вывести(ПроцессорКомпоновки);
	
КонецПроцедуры

Способ 3. Связи нескольких наборов данных (когда все метаданные существуют)

Выясним причину, по которой иногда требуется отключать соединение таблиц, даже если метаданные присутствуют во всех базах. Это необходимо для оптимизации производительности: тяжелые соединения с большими таблицами не должны выполняться, если пользователь отключил параметр отчета.

В этом случае запрос разделяется на два отдельных набора данных внутри одной схемы:

  1. НаборДанных1 (Базовый): выбирает основные данные.
  2. НаборДанных2 (Дополнительный): обращается к регистру сведений.
  3. На вкладке «Связи наборов данных» настраивается связь между ними по ключевым полям.
  4. В колонке «Условие связи» указывается проверка значения параметра: &ИспользоватьДоработку = Истина.

Если условие не выполняется, компоновщик данных не формирует запрос ко второму набору данных, экономя время выполнения отчета.

Способ 4. Использование механизма расширений (.cfe)

Проанализируем архитектурное решение задачи. Если кастомный регистр сведений создается в расширении конфигурации, наиболее правильным подходом будет не усложнять типовой отчет программным кодом, а вынести доработанную схему СКД в то же самое расширение. При этом в базовой конфигурации отчет остается неизменным, а в расширении переопределяется макет со всеми необходимыми связями к новым объектам.

← На главную