Как найти номенклатуру, обладающую всеми свойствами из заданного списка

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

В практике разработки на платформе 1С:Предприятие часто возникает задача фильтрации данных по принципу «И». Типичный пример: необходимо найти в справочнике Номенклатура те позиции, которые обладают всем набором заданных свойств одновременно (например, товар должен быть и «деревянным», и «высоким», и «прочным»).

Стандартное использование оператора В (&СписокСвойств) в условии запроса не решает эту задачу, так как оно работает по логике «ИЛИ» — запрос вернет все объекты, у которых есть хотя бы одно свойство из списка. В теории баз данных такая задача называется «реляционным делением». Рассмотрим подробнее несколько способов её решения, проанализируем их эффективность и разберем конкретные примеры кода.

Метод 1: Группировка и условие ИМЕЮЩИЕ (оптимальный вариант)

Этот способ является наиболее универсальным и простым в реализации. Основная идея заключается в том, чтобы сначала отобрать все записи, подходящие под условие «хотя бы одно свойство из списка», а затем сгруппировать их по объекту и посчитать, сколько именно свойств из списка нашлось для каждого объекта. Если количество найденных уникальных свойств совпадает с количеством элементов в исходном фильтре, значит, объект обладает полным набором.

Разберем этот метод по шагам:

  1. Передаем список искомых свойств в параметр запроса &СписокСвойств.
  2. В коде на встроенном языке определяем количество элементов в этом списке.
  3. Выполняем запрос с группировкой.

// Предположим, СписокСвойств - это Массив или СписокЗначений с искомыми характеристиками
КоличествоСвойств = СписокСвойств.Количество();

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЗначенияСвойств.Объект КАК Объект
    |ИЗ
    |	РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойств
    |ГДЕ
    |	ЗначенияСвойств.Значение В(&СписокСвойств)
    |СГРУППИРОВАТЬ ПО
    |	ЗначенияСвойств.Объект
    |ИМЕЮЩИЕ
    |	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ЗначенияСвойств.Значение) = &КоличествоСвойств";

Запрос.УстановитьПараметр("СписокСвойств", СписокСвойств);
Запрос.УстановитьПараметр("КоличествоСвойств", КоличествоСвойств);

РезультатЗапроса = Запрос.Выполнить();

Важный нюанс: использование КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...) обязательно. Это страхует нас от ситуаций, когда в регистре сведений по какой-то причине могут быть задублированы записи для одного и того же объекта с одинаковым значением свойства. Если мы уверены в уникальности данных, можно использовать просто КОЛИЧЕСТВО(*), что работает несколько быстрее.

Метод 2: Использование временных таблиц для оптимизации

Как было отмечено в обсуждении, оператор В (...) может работать медленно, если список значений очень большой или если СУБД не может эффективно построить план запроса. В таких случаях профессиональным стандартом считается предварительная выгрузка списка свойств во временную таблицу.

Проанализируем ситуацию: когда мы помещаем список во временную таблицу, мы можем проиндексировать её, что позволит СУБД использовать Внутреннее соединение (Inner Join) вместо тяжелого фильтра В.


Запрос = Новый Запрос;
Запрос.МенеджерВременных Таблиц = Новый МенеджерВременныхТаблиц;

// Шаг 1: Помещаем список во временную таблицу
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Свойства.Значение КАК Свойство
    |ПОМЕСТИТЬ ВТ_Критерии
    |ИЗ
    |	&СписокСвойств КАК Свойства
    |ИНДЕКСИРОВАТЬ ПО
    |	Свойство";
    
Запрос.УстановитьПараметр("СписокСвойств", СписокСвойств);
Запрос.Выполнить();

// Шаг 2: Основной запрос с соединением
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЗначенияСвойств.Объект КАК Объект
    |ИЗ
    |	РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойств
    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Критерии КАК ВТ_Критерии
    |		ПО ЗначенияСвойств.Значение = ВТ_Критерии.Свойство
    |СГРУППИРОВАТЬ ПО
    |	ЗначенияСвойств.Объект
    |ИМЕЮЩИЕ
    |	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ЗначенияСвойств.Значение) = &КоличествоСвойств";

Запрос.УстановитьПараметр("КоличествоСвойств", СписокСвойств.Количество());
Результат = Запрос.Выполнить();

Этот метод значительно стабильнее на больших объемах данных, так как оптимизатор СУБД гораздо лучше справляется с соединением двух проиндексированных таблиц.

Метод 3: Множественные внутренние соединения (N-1 соединений)

Если количество искомых свойств невелико и заранее известно (например, всегда ровно 2 или 3), можно использовать последовательные соединения таблицы самой с собой. Рассмотрим пример для поиска номенклатуры, которая одновременно «Деревянная» И «Прочная».


ВЫБРАТЬ
    Т1.Объект
ИЗ
    РегистрСведений.ЗначенияСвойствОбъектов КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК Т2
    ПО Т1.Объект = Т2.Объект
ГДЕ
    Т1.Значение = &Свойство1
    И Т2.Значение = &Свойство2

Преимущества: такой запрос часто выполняется быстрее всего, так как СУБД использует индексы по значениям свойств максимально эффективно, отсекая ненужные записи без выполнения группировки.

Недостатки: метод неудобен, если количество свойств динамическое. В этом случае придется программно формировать текст запроса, добавляя новые ВНУТРЕННЕЕ СОЕДИНЕНИЕ для каждого свойства.

Производительность и индексы

Выясним причину, по которой подобные запросы могут «тормозить» на реальных базах. В типовых конфигурациях 1С основной индекс регистра ЗначенияСвойствОбъектов обычно строится по полям Объект + Свойство.

При поиске по Значению (как в нашем случае), стандартного индекса может быть недостаточно. Проанализируем структуру регистра:

Совет: Если вы планируете часто использовать такой поиск в крупной базе данных, убедитесь, что для измерения Значение (или Свойство, в зависимости от логики) в метаданных регистра установлено свойство Индексировать.

Практические рекомендации

Подводя итог, разберем, какой метод выбрать:

  1. Для динамического количества свойств (пользователь сам выбирает их в списке на форме) — используйте Метод 2 с временной таблицей и ИМЕЮЩИЕ КОЛИЧЕСТВО. Это самый безопасный и предсказуемый вариант.
  2. Для жестко заданных фильтров в коде (2–3 свойства) — можно использовать Метод 3 (внутренние соединения), так как он обеспечит максимальную скорость.
  3. Всегда проверяйте, чтобы в регистре, по которому идет поиск, были соответствующие индексы. Без них даже самый изящный запрос превратится в проблему производительности.
  4. При использовании группировки не забывайте про РАЗЛИЧНЫЕ внутри агрегатной функции КОЛИЧЕСТВО, чтобы избежать ложных срабатываний из-за дублей данных.

Рассмотренные подходы позволяют эффективно решать задачу поиска объектов с полным набором характеристик, обеспечивая баланс между читаемостью кода и скоростью работы системы.

← На главную