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