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