gifts2017

Подстановка и добавление в отчет на СКД произвольных значений, которые нельзя получить запросом

Опубликовал Дмитрий Шпаковский (Godman) в раздел Программирование - Практика программирования

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

Первый подход условно назовем "Две схемы". Суть в том, что в отчете создаются две схемы компоновки данных. Первая строится на основе запроса и выгружается в ТаблицуЗначений (или ДеревоЗначений), вторая - схема на основе объекта. В неё помещается эта самая ТаблицаЗначений, которую предварительно модифицируем по своему усмотрению. С первой схемой все понятно, рассмотрим вторую схему подробнее:

 Настройка отчета "Две схемы"

Обратите внимание, что в этой схеме набор данных - Объект и все поля создаются вручную. Также обязательно нужно указать типы полей компоновки, иначе отбор по ссылочным типам работать не будет. И еще эта схема является основной схемой отчета, т.е. в ней мы создаем макет, отборы и т.д.

// Процедура модуля объекта отчета
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
	
	СтандартнаяОбработка = Ложь;
    
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
	
	МакетКомпоновки = КомпоновщикМакета.Выполнить(
		ПолучитьМакет("СхемаКомпоновкиДанных"),                      // Первая схема компоновки
		ПолучитьМакет("СхемаКомпоновкиДанных").НастройкиПоУмолчанию, // Получаем настройки из первой схемы компоновки
		ДанныеРасшифровки,
		,
		Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений")
	);
	
	ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
	ПроцессорКомпоновки.Инициализировать(МакетКомпоновки,,ДанныеРасшифровки);
	
	ДокументРезультат.Очистить();
	
	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
    ТаблицаРезультат = Новый ТаблицаЗначений;
    ПроцессорВывода.УстановитьОбъект(ТаблицаРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);	
	
	// Выгрузили результат компоновки в таблицу значений. Теперь можем делать с ней все, что хотим: 
	ТаблицаРезультат.Колонки.Добавить("НоваяКолонка"); // Не забываем добавить поле "НоваяКолонка" и в набор данных схемы "СхемаКомпоновкиДанныхВнешняя"
	
	Если ТаблицаРезультат.Колонки.Найти("Наименование") <> Неопределено Тогда
		Для каждого СтрокаТЗ Из ТаблицаРезультат Цикл
			
			Если ЗначениеЗаполнено(СтрокаТЗ.Наименование) Тогда
				СтрокаТЗ.Наименование = СтрокаТЗ.Наименование + " ["+СтрДлина(СокрЛП(СтрокаТЗ.Наименование))+"]";
			КонецЕсли;  
			
			СтрокаТЗ.НоваяКолонка = СтрокаТЗ.Ссылка.УникальныйИдентификатор();
			
		КонецЦикла;
	КонецЕсли; 
		
	// Возвращаем модифицированную таблицу значений обратно в СКД, но в другую схему 
	ВнешнийНаборДанных = Новый Структура;
	ВнешнийНаборДанных.Вставить("ВнешнийНаборДанных", ТаблицаРезультат);
	
	МакетКомпоновки = КомпоновщикМакета.Выполнить(
		ПолучитьМакет("СхемаКомпоновкиДанныхВнешняя"),  // Вторая схема компоновки
		КомпоновщикНастроек.ПолучитьНастройки(),        // Текущие настройки отчета
		ДанныеРасшифровки,
	);
	ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешнийНаборДанных, ДанныеРасшифровки);
	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
	ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
	ПроцессорВывода.Вывести(ПроцессорКомпоновки);	
	
КонецПроцедуры

Обратим внимание на один важный момент. Схемы мы используем две, а настройка отчета - одна.

На примере: допустим мы отбираем группу Номенклатуры "Продукты". Сначала отрабатывает первая схема по своим типовым настройкам, т.е. в таблицу значений выгружается вся номенклатура без отбора по "Продуктам". Затем вторая схема получает всю таблицу значений и уже в ней отбираются продукты. Для ускорения процесса очень не помешало бы и в первой схеме использовать тот же самый отбор. Для этого можно поступить следующим образом: вместо строки кода

ПолучитьМакет("СхемаКомпоновкиДанных").НастройкиПоУмолчанию, // Получаем настройки из первой схемы компоновки

получаем настройки не из первой схемы, а из отчета:

Настройки = КомпоновщикНастроек.ПолучитьНастройки();  

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

// из УТ11
ОбщегоНазначенияКлиентСервер.ИзменитьЭлементыОтбора(Настройки.Отбор,"Наименование",,,,Ложь);
ОбщегоНазначенияКлиентСервер.ИзменитьЭлементыОтбора(Настройки.Отбор,"НоваяКолонка",,,,Ложь);

 

Второй подход, назовем "Подстановка", заключается в том, что вывод результата компоновки происходит в цикле построчно и в этот момент заменяются значения элемента результата на произвольные.

Схема используется одна, особенность только в том, что в обработчик модуля объекта отчета ПриКомпоновкеРезультата подставляется следующий код:

Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
	
    СтандартнаяОбработка = Ложь;
    
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();  
    
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
	
	МакетКомпоновки = КомпоновщикМакета.Выполнить(
		СхемаКомпоновкиДанных,
		Настройки,
		ДанныеРасшифровки
	);
	
	ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
	ПроцессорКомпоновки.Инициализировать(
		МакетКомпоновки,
		,
		ДанныеРасшифровки
	);
	
	ДокументРезультат.Очистить();
	
	
	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
	ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
	
	ПроцессорВывода.НачатьВывод();
	
	Пока Истина Цикл
		
		ЭлементРезультата = ПроцессорКомпоновки.Следующий();
		
		Если ЭлементРезультата = Неопределено Тогда
			Прервать;
		КонецЕсли; 
		
		Если ЭлементРезультата.ЗначенияПараметров.Количество() > 0 Тогда 
			
			ИмяПараметра_СсылкаПредставление = Неопределено;
			
			// Поскольку имена параметров зашиты как "П1, П2..." то нужно понять какой из них нам нужен
			// Для этого идем в макеты и ищем имя параметра в выражении к параметру
			Для каждого ПараметрМакета Из МакетКомпоновки.Макеты[ЭлементРезультата.Макет].Параметры Цикл
				
				Если ТипЗнч(ПараметрМакета) = Тип("ПараметрОбластиВыражениеКомпоновкиДанных") Тогда
					
					Если Найти(ПараметрМакета.Выражение,"СсылкаПредставление") > 0 Тогда
						ИмяПараметра_СсылкаПредставление = ПараметрМакета.Имя;
					КонецЕсли; 
					
					Если Найти(ПараметрМакета.Выражение,"НоваяКолонка") > 0 Тогда
						ИмяПараметра_НоваяКолонка = ПараметрМакета.Имя;
					КонецЕсли; 
					
					Если Найти(ПараметрМакета.Выражение,"Наименование") > 0 Тогда
						ИмяПараметра_Наименование = ПараметрМакета.Имя;
					КонецЕсли; 
					
				КонецЕсли;	
			
			КонецЦикла; 
			
			Если ИмяПараметра_СсылкаПредставление <> Неопределено Тогда
				// Заменяем значения полей на свои произвольные
				Наименование = ЭлементРезультата.ЗначенияПараметров[ИмяПараметра_Наименование].Значение;
				
				ЭлементРезультата.ЗначенияПараметров[ИмяПараметра_Наименование].Значение = Наименование +" ["+СтрДлина(СокрЛП(Наименование))+"]";
				
				ЭлементРезультата.ЗначенияПараметров[ИмяПараметра_НоваяКолонка].Значение = 
					ПолучитьСсылкуИзПредставления(ЭлементРезультата.ЗначенияПараметров[ИмяПараметра_СсылкаПредставление].Значение).УникальныйИдентификатор();
				
			КонецЕсли; 
			
		КонецЕсли;
		
		ПроцессорВывода.ВывестиЭлемент(ЭлементРезультата);
		
	КонецЦикла;   
		
	ПроцессорВывода.ЗакончитьВывод();
	ДокументРезультат.ОтображатьСетку=ложь;
	ДокументРезультат.ОтображатьЗаголовки=ложь;
	ДокументРезультат.ТолькоПросмотр = Истина;
	ДокументРезультат.Показать();
	
КонецПроцедуры

Для этого кода требуются пояснения. Есть один неприятный момент. Поле "Ссылка", которое мы замечательно получили в запросе с типом Справочник.Номенклатура.Ссылка компоновка легко и непринужденно превратила в поле "СсылкаПредставление" уже строкового типа, а саму ссылку запихнула куда-то в расшифровку. Поэтому возникает вопрос: как получить ссылку из её строкового представления. В данном примере это сделано примитивно:

Функция ПолучитьСсылкуИзПредставления(ПредставлениеНоменклатуры)
	Возврат Справочники.Номенклатура.НайтиПоНаименованию(ПредставлениеНоменклатуры);
КонецФункции

Однако если ссылка - Документ, то это уже сложнее. Хотя, если документ известного типа, то представление легко парсится в ссылку с помощью типовых строковых функций УТ11 (если нужно - выложу функцию). Но если ссылка с составным типом, то даже не знаю как получить из представления ссылки неизвестного типа саму ссылку. Возможно, кто-то знает как достать ссылку из расшифровки, поделитесь плиз.


Итак, у каждого из подходов есть свои плюсы и минусы.

 Подход "Две схемы"

  • Плюсы: 
    • Более полные возможности интерактивной компоновки: измененные данные можно полноценно фильтровать, группировать и т.д.
    • Нет проблем с получением ссылочных типов данных
  • Минусы:
    • Медленно формируется (выгрузка в таблицу значений, перебор, обратная загрузка)
    • Возможны трудности с отбором по изменяемым полям, поскольку настройка одна, а схемы две и нужно понимать что и как будет отбираться
    • Вторую схему нужно делать вручную - прописывать поля и их типы. Если полей много, то можно выгрузить первую схему в xml, слегка модифицировать и загрузить во вторую 

 Подход "Подстановка" 

  • Плюсы: 
    • Быстрота формирования отчета (нет никаких промежуточных схем и выгрузов в таблицы значений)
    • Не нужно заботиться о пользовательском отборе, поскольку схема одна - настройка одна. Нужно только помнить, что нельзя отбирать по изменяемым полям
  • Минусы:
    • Меньше возможности интерактивной компоновки: измененные данные нельзя отбирать, группировать и т.д., поскольку замена значений происходит уже после всех процедур компоновки в момент вывода данных
    • Возможны проблемы с получением ссылок данных из их представления

Я использую в практике отчеты с обоими вариантами замены значений. Подстановка намного быстрее работает, но не всегда ее можно применить.

Надеюсь, что пост получилось написать понятным языком и кому-нибудь пригодится.

Критика, комментарии, рецензии и помощь приветствуются.

Спасибо за внимание. 

Скачать файлы

Наименование Файл Версия Размер Кол. Скачив.
Пример замены значений в СКД (две схемы)
.erf 8,41Kb
06.03.15
14
.erf 1 8,41Kb 14 Скачать
Пример замены значений в СКД (подстановка)
.erf 6,94Kb
06.03.15
16
.erf 1 6,94Kb 16 Скачать

См. также

Подписаться Добавить вознаграждение

Комментарии

1. т т (MadMix) 06.03.15 11:33
Непонятно, зачем вообще все это нужно. В СКД можно вызывать функции общих модулей - то есть обработка полей может быть насколько угодно сложной. Единственный минус - нужно вносить изменения в конфигурацию.
zqzq; slazzy; +2 Ответить 4
2. Артем Целовальников (slazzy) 06.03.15 12:05
(1) MadMix, собственно плюсую вопрос ) непонятно зачем это надо, в смысле понятно, что бывают такие ситуации, когда надо. Но в 95% ситуаций можно сделать всё в СКД, а тут в примерах костыль на костыле
3. Rauan Sandybaev (sandybaev) 06.03.15 14:16
согласен, да можно все попросить в общем модуле. и читабельность получится лучше.
но пусть +
4. Андрей Иваненко (AnderWonder) 06.03.15 15:35
А зачем в первом подходе использовать две схемы? Можно же сразу формировать нужную таблицу значений полностью программно, в т.ч. на основании какого-либо запроса и передавать её как источник данных для второй схемы. Т.е. первая схема не нужна.
5. TMV 07.03.15 08:18
(4) AnderWonder, на мой взгляд проще передать параметры и условия от второй схеме первой, нежели от схемы запросу, да и код будет получше выглядеть, имхо.
6. Александр Крынецкий (echo77) 07.03.15 09:36
(0) Я не вижу в представленном примере ни одной фишки, которую нельзя было бы сделать в одной схеме, используя функционал только СКД.

Составить строку с длиной строки в скобках пожалуйста:

Функция языка выражения СКД ДлинаСтроки
Наименование +" ["+ДлинаСтроки(Наименование))+"]"

Вывести уникальный идентификатор(UUID) ссылки - пожалуйста:
XMLСтрока(Ссылка)
7. Дмитрий Шпаковский (Godman) 09.03.15 02:49
(1) MadMix, (2) slazzy,
А вы пробовали использовать глобальные функции в СКД-отчете с хорошим количеством строк и парой временных таблиц? А я пробовал. Окончания формирования не дождался. А дело в том, что, если в функции общего модуля идёт обращение к базе данных, то это обращение происходит после того, как получены все данные для отчёта. По сути для каждой строки запрос будет выполняться ещё раз.
Кроме того, действительно, случаются ситуации, когда нельзя изменять конфигурацию, и уж тем более выносить некие функции в глобальный модуль, отрывая их от "родных" общих модулей. И еще не очень понятно как быть в ситуации, когда в такую глобальную функцию нужно передать структуру параметров и т.д.
Т.е. ситуаций бывает много, наверное мне стоило вынести эти ситуации в начало поста.
8. Дмитрий Шпаковский (Godman) 09.03.15 02:55
(6) echo77,
Представленный пример я писал быстро и что сразу пришло в голову, то и написал.
Однако и ваш пример
Наименование +" ["+ДлинаСтроки(Наименование))+"]"
не работает. Более того, функция ДлинаСтроки появилась только в 8.2.14, а у меня есть базы на 13.219
А вот за XMLСтрока() спасибо, как-то не задумывался что эта функция глобального контекста
9. Александр Крынецкий (echo77) 09.03.15 08:31
(8) Godman, ошибка в тексте выражения.
Не вижу смысла сидеть на 8.2 и использовать древние релизы платформы.
Кстати, в 8.3 еще больше интересностей в СКД появилось, но 8.3 пока не пользуется большой популярностью.
10. Дмитрий Шпаковский (Godman) 09.03.15 13:23
(9) echo77,
Я имел в виду что пример в принципе не работает, даже так:
""+ДлинаСтроки(Наименование)
Ну не нравится ему конкатенация вместе с функциями.
11. Артем Целовальников (slazzy) 09.03.15 17:30
(9), простите я точно попал в 2015 год? Просто я уже как года 2 практически не видел 8.2, абсолютно у всех стоит 8.3 и уже очень давно. Более того вряд ли вообще существуют хоть сколько-то адекватные причины не переходить на 8.3

(7), я прекрасно знаю как вызываются эти строки. Я написал всего лишь о том, что вообще крайне редки ситуации, в которых может понадобиться вручную добавлять какую-то колонку и придется делать это программно вне СКД. Почти наверняка это на самом деле можно сделать в СКД и сделать удобно, просто вероятно человек не знает все особенности СКД или не хочет/не может писать сложные запросы.
Если же реально нужна какая-то хитрая и сложная колонка, то что мешает изначально БЕЗ использования второго СКД просто собрать ТЗ и загрузить её в СКД уже с этой колонкой?
В Вашем же примере всё равно используется цикличное обращение к базе данных для каждой строки.
  ЭлементРезультата.ЗначенияПараметров[ИмяПараметра_НоваяКолонка].Значение = 
                    ПолучитьСсылкуИзПредставления(ЭлементРезультата.ЗначенияПараметров[ИмяПараметра_СсылкаПредставление].Значение).УникальныйИдентификатор(


Так что тоже не совсем понятно в чем тут выигрыш.
12. Александр Крынецкий (echo77) 09.03.15 21:19
(10) Godman, Да, я рукожоп. Чтобы это заработало, необходимо привести число к типу строка.
Правильное выражение выглядит так:
[Наименование] + " [" + Строка(ДлинаСтроки([Наименование])) + "]"


(11) Собрать ТЗ иногда надо на основании отборов, которые установил пользователь. Как я понял, автор об этом хочет сказать
13. Сергей Ожерельев (Поручик) 10.03.15 17:04
(9) Ну ты ржачно загнул, caro collega. БП 3.0 и УТ 11.1 уже почти год требуют исключительно 8.3.
14. Николай ~ ~ (kuzyara) 11.03.15 11:03
Спасибо за публикацию. Пытался самостоятельно реализовать оба подхода на практике - получились костыли, несовместимые с жизнью.
Godman; fomix; +2 Ответить
15. Сергей Артюхов (Serg3141) 11.03.15 11:16
(11) у меня на работе используется 8.2 да еще и в режиме совместимости. Конфа настолько перековеркана сторонними программистами, что страшно рассказывать. В связи с чем ни кто не стремится переходить на что-либо новое. И меняющееся законодательство не пугает.
16. Константин Юрин (kostyaomsk) 11.03.15 20:42
(1) MadMix, минус в том, что не всегда эти самые общие модули конфигурации могут быть открыты. Если конфа базовая - все, дело труба. А если даже нетиповая, то приходится спрашивать разрешение у руководителя проекта на добавление-модификацию общих модулей...
В любом случае универсальность теряется.
Из минусов подхода
какую-то цифру или значение не получается получить в запросе, а требуются вызовы каких-то процедур и функций
чаще всего обычно строку из дат и чисел не получается. Решение то с подобной идеей хорошая, но пользоваться нужно в крайнем случае, когда очень надо чтоб отчет СКД был универсальным и зависел только от релиза конфигурации. Трудоемкость на час-два минимум возрастет только на изучение и подгонку.
17. Дмитрий Шпаковский (Godman) 12.03.15 00:01
(11) slazzy, Про повсеместность 8.3 вы зря. Типовые базы типа бух 3 и торг 11.1, да крутятся на отдельных серверах с 8.3. Но есть целый зоопарк отраслевых решений, типа альфа-авто, ликероводочный завод на обычных интерфейсах со своими доработками которые то ли будут работать на 8.3, то ли нет - проверять себе дороже. Но это вообще темы не касается.
Что касается перебора строк при выводе результата, то в методе "подстановка" никакой цикличности нет, просто идет вывод результата построчно. В "двух схемах" выгрузка в ТЗ и загрузка из нее съедают основное время. А вот когда мы используем глобальную функцию, то боюсь в этом случае запрос выполняется действительно циклично: столько раз, сколько нужно рассчитать значение функции. Я, правда, не уверен на 100%, что это так и есть, но на практике с глобальной функцией получалось неприемлемо долго.

В любом случае, ситуаций, когда что-то нельзя получить в запросе очень мало. И даже если они возникают, чаще всего удается объяснить заказчику, что так ему не нужно.

(12) echo77, Да, действительно, две схемы используются потому, что на них легко "накинуть" один и тот же пользовательский отбор.
18. Rom Shpakoff (Lancelot-2M) 22.03.15 15:21
Регулярно использую вариант 2 схемы, но параметры и отборы доступные пользователю нужно делать по первой схеме чтобы ограничить выборку данных и не затягивать формирование отчета. По поводу - нафига ваще - а ряд формул расчета значений в строках оперируют итоговыми значениями других ресурсов
19. Дмитрий Шпаковский (Godman) 23.03.15 11:51
(18) Lancelot-2M,
Все правильно, только в моем примере и вообще концептуально, пользовательский отбор накладывается на обе схемы сразу, но за основу берется вторая схема, поскольку в ней есть новые рассчитанные значения, по которым мы тоже, как правило, хотим отбор.
20. Алексей (alest) 07.04.15 15:09
(17) Godman,
Что касается перебора строк при выводе результата, то в методе "подстановка" никакой цикличности нет

Т.е. вызов глобальной функции плохо, а обращение к БД в цикле методом НайтиПоНаименованию - ОК?
Имеется ввиду, что вы в цикле обращаетесь каждый раз к базе. Таким макаром СКД и запросы использовать вообще нет смысла.
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Ссылка.Наименование);

?
21. Дмитрий Шпаковский (Godman) 08.04.15 13:31
(20) alest, При вызове глобальной функции будет как минимум такое же количество обращений к базе данных. Более того, это толком нигде не описано, но у меня есть большое подозрение, что весь запрос может выполняться столь раз, сколько будет обращений к глобальной функции. Есть еще ряд ограничений, которые накладывает вызов глобальной функции. Они описаны выше. Могу и повторить: 1. Не всегда возможно добавлять в конфигурацию глобальный модуль со своей функцией. 2. В эту функцию мы не сможем передать сложные параметры вроде структур. 3. Собственноручное наблюдение: если глобальная функция не очень простая, то с ней отчет формируется ооочень медленно.
22. Петр Базелюк (pbazeliuk) 02.11.16 20:42
(1) MadMix, действительно костыль на костыле. Но можно развить идею, например, создать свой ПроцессорВывода, который выводит не только в ДеревоЗначений, ТаблицуЗначений и ТабличныйДокумент, а и в другие прикладные типы.
Для написания сообщения необходимо авторизоваться
Прикрепить файл
Дополнительные параметры ответа