Как в СКД сделать отбор номенклатуры по нескольким сегментам с условием «И»?

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

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

Выясним причину поведения СКД

Проанализируем структуру хранения данных в типовых конфигурациях (например, «1С:Управление торговлей 11» или «1С:ERP»). Информация о вхождении товаров в сегменты хранится в регистре сведений НоменклатураСегмента. Каждая строка этого регистра содержит связку из двух полей: Номенклатура и Сегмент.

Когда в пользовательских настройках СКД создается группа условий «И» с несколькими сегментами, платформа формирует следующий фрагмент SQL-запроса:

ГДЕ Таблица.Сегмент = &Сегмент1 И Таблица.Сегмент = &Сегмент2

Так как одна конкретная запись регистра не может содержать сразу два разных значения в поле Сегмент, такое условие всегда возвращает ложь, и в итоговую выборку ничего не попадает.

Способ 1. Использование запроса с группировкой и секцией ИМЕЮЩИЕ (HAVING)

Рассмотрим наиболее универсальный и эффективный подход на уровне запроса, который работает для любого количества выбранных сегментов. Идея состоит в том, чтобы отобрать все записи, входящие в выбранный список сегментов, сгруппировать их по номенклатуре и оставить только те позиции, количество различных сегментов у которых совпадает с количеством выбранных условий.

Посмотрим на пример построения такого запроса:


ВЫБРАТЬ
	НоменклатураСегмента.Номенклатура КАК Номенклатура
ИЗ
	РегистрСведений.НоменклатураСегмента КАК НоменклатураСегмента
ГДЕ
	НоменклатураСегмента.Сегмент В(&МассивСегментов)

СГРУППИРОВАТЬ ПО
	НоменклатураСегмента.Номенклатура

ИМЕЮЩИЕ
	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ НоменклатураСегмента.Сегмент) = &КоличествоВыбранныхСегментов

В данном случае параметры &МассивСегментов и &КоличествоВыбранныхСегментов передаются в запрос перед выполнением компоновки макета.

Способ 2. Программная обработка отбора в модуле отчета

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

Схема выполнения выглядит следующим образом:

  1. В событии ПриКомпоновкеРезультата считываем выбранные пользователем элементы отбора по полю Сегмент.
  2. Получаем массив выбранных сегментов и их количество.
  3. Выполняем отдельный запрос к регистру сведений с секцией ИМЕЮЩИЕ (как показано в первом способе) и получаем массив ссылок на номенклатуру, входящую во все эти сегменты одновременно.
  4. Отключаем исходный отбор по сегментам в настройках компоновки макета.
  5. Устанавливаем программный отбор вида Номенклатура В (&МассивНайденнойНоменклатуры).

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


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

	// Выполняем стандартную компоновку с обновленными настройками
	КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
	МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
	
	ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
	ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , ДанныеРасшифровки);
	
	ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВБумажныйДокумент;
	ПроцессорВывода.УстановитьДокумент(РезультатДокумент);
	ПроцессорВывода.Вывести(ПроцессорКомпоновки);

КонецПроцедуры

Способ 3. Использование дополнительных реквизитов и свойств

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

В этом случае данные хранятся как отдельные свойства номенклатуры. В СКД отбор по разным дополнительным реквизитам работает штатно через стандартные группы условий «И», так как каждое свойство является отдельным полем или значением в таблице характеристик.

← На главную