При работе с механизмами 1С:Предприятия, такими как дополнительные реквизиты и сведения (архитектура Entity-Attribute-Value или EAV), разработчики часто сталкиваются с задачей поиска объекта, у которого одновременно задано несколько конкретных значений свойств. Например, нужно найти характеристику номенклатуры, у которой «Длина» равна 1, «Ширина» равна 2, а «Цвет» — «Серый».
Основная сложность заключается в том, что в базе данных эти свойства хранятся в виде строк табличной части или записей регистра сведений. Это значит, что для одной характеристики существует несколько записей в таблице реквизитов. Прямое условие ГДЕ Свойство = &Свойство1 И Свойство = &Свойство2 никогда не сработает, так как в одной строке не могут находиться два разных значения свойства одновременно.
В этой статье мы подробно разберем, как правильно и эффективно строить такие запросы, проанализируем различные подходы и выберем наиболее производительный вариант.
Этот метод считается классическим и наиболее наглядным. Мы несколько раз присоединяем одну и ту же таблицу дополнительных реквизитов к основной таблице (или саму к себе), каждый раз накладывая условие на конкретное свойство. Рассмотрим, как это выглядит в коде:
ВЫБРАТЬ
Характеристики.Ссылка КАК Характеристика
ИЗ
Справочник.ХарактеристикиНоменклатуры КАК Характеристики
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыДлина
ПО Характеристики.Ссылка = ДопРеквизитыДлина.Ссылка
И ДопРеквизитыДлина.Свойство = &ДлинаСвойство
И ДопРеквизитыДлина.Значение = &ДлинаЗначение
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыШирина
ПО Характеристики.Ссылка = ДопРеквизитыШирина.Ссылка
И ДопРеквизитыШирина.Свойство = &ШиринаСвойство
И ДопРеквизитыШирина.Значение = &ШиринаЗначение
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизитыЦвет
ПО Характеристики.Ссылка = ДопРеквизитыЦвет.Ссылка
И ДопРеквизитыЦвет.Свойство = &ЦветСвойство
И ДопРеквизитыЦвет.Значение = &ЦветЗначение
Плюсы метода:
Минусы метода:
JOIN.Проанализируем более элегантный и быстрый способ. Вместо того чтобы соединять таблицы, мы выберем все записи, которые подходят хотя бы под одно условие, сгруппируем их по объекту-владельцу и отсечем те объекты, у которых количество найденных совпадений меньше, чем общее количество искомых свойств.
Рассмотрим реализацию этого алгоритма:
ВЫБРАТЬ
ДопРеквизиты.Ссылка КАК Характеристика
ИЗ
Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
ГДЕ
(ДопРеквизиты.Свойство = &Свойство1 И ДопРеквизиты.Значение = &Значение1)
ИЛИ (ДопРеквизиты.Свойство = &Свойство2 И ДопРеквизиты.Значение = &Значение2)
ИЛИ (ДопРеквизиты.Свойство = &Свойство3 И ДопРеквизиты.Значение = &Значение3)
СГРУППИРОВАТЬ ПО
ДопРеквизиты.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ДопРеквизиты.Свойство) = &КоличествоИскомыхСвойств
Разберем по шагам, как это работает:
ГДЕ мы указываем все пары «Свойство-Значение», используя оператор ИЛИ. Это позволяет СУБД эффективно использовать индексы по свойствам.Ссылка (владельцу реквизитов).ИМЕЮЩИЕ КОЛИЧЕСТВО(...) = N гарантирует, что в результат попадут только те характеристики, для которых нашлось ровно N совпадений. Если для объекта нашлось только 2 свойства из 3 запрошенных, он будет отсеян.Важное замечание: Рекомендуется использовать КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Свойство), чтобы избежать проблем, если в данных по ошибке задублировались строки с одинаковым свойством и значением.
Иногда требуется поэтапная фильтрация, особенно если данных очень много и нужно максимально упростить работу оптимизатору СУБД. Мы можем сначала отобрать ссылки, подходящие по первому свойству, поместить их во временную таблицу, а затем отобрать из них те, что подходят по второму, и так далее.
Пример реализации через временные таблицы:
ВЫБРАТЬ
ДопРеквизиты.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_Фильтр
ИЗ
Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
ГДЕ
ДопРеквизиты.Свойство = &Свойство1 И ДопРеквизиты.Значение = &Значение1
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ДопРеквизиты.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_ИтоговыйФильтр
ИЗ
Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
ГДЕ
ДопРеквизиты.Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ_Фильтр)
И ДопРеквизиты.Свойство = &Свойство2
И ДопРеквизиты.Значение = &Значение2
Этот метод кажется громоздким, но в некоторых случаях на PostgreSQL или очень больших объемах MS SQL Server он показывает стабильное время выполнения, так как СУБД работает с уже сокращенными наборами данных на каждом шаге.
Проанализируем ситуацию с точки зрения ресурсов системы. Как показывают практические замеры (в том числе приведенные в обсуждении темы), второй способ (с группировкой и HAVING) зачастую оказывается в несколько раз быстрее классических соединений. Это связано с тем, что СУБД выполняет одно сканирование индекса вместо построения множества временных структур для сопоставления строк.
Почему соединения могут работать медленно?
Когда мы используем JOIN, СУБД вынуждена сопоставлять каждую найденную строку с каждой. Если по первому фильтру найдено 1000 объектов и по второму 1000, системе нужно проверить множество комбинаций. Группировка же работает линейно по одному набору данных.
Выясним причины, по которым запрос может работать медленно даже при правильной структуре:
Значение в таблице дополнительных реквизитов почти всегда имеет составной тип. Поиск по такому полю сложнее для СУБД, чем поиск по конкретной ссылке.Свойство и Значение. В типовых конфигурациях это обычно настроено по умолчанию.Рассмотрим ситуацию, когда свойств нет в объекте. Если вам нужно найти объекты, у которых нет определенных свойств, лучше всего использовать левое соединение с проверкой на ЕСТЬ NULL.
Для большинства задач поиска в 1С оптимальным является следующий алгоритм:
ИЛИ для всех искомых пар «Свойство-Значение».СГРУППИРОВАТЬ ПО объекту-владельцу.ИМЕЮЩИЕ КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Свойство) = &N, где N — количество фильтров.Такой подход обеспечивает максимальную скорость работы, легкость масштабирования (нужно просто менять количество условий в ИЛИ и параметр N) и стабильность при росте объема базы данных.