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