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