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