Как найти характеристику номенклатуры по нескольким дополнительным реквизитам в 1С?

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

При разработке в среде 1С:Предприятие часто возникает задача подбора или поиска объектов по набору их свойств. Типичный пример — поиск конкретной характеристики номенклатуры по набору её дополнительных реквизитов (например, когда нам нужно найти характеристику, у которой одновременно «Длина» равна 10, «Ширина» равна 20, а «Высота» равна 30). На первый взгляд задача кажется простой, но стандартные подходы с использованием простых условий в запросе часто приводят к неверным результатам или крайне низкой производительности.

В этой статье мы подробно разберем, почему простые решения не работают, детально изучим классический и надежный метод группировки с условием ИМЕЮЩИЕ, а также рассмотрим альтернативный высокопроизводительный метод динамического соединения таблиц для высоконагруженных систем.

Почему простое условие «ГДЕ» с оператором «В» возвращает неверные результаты?

Рассмотрим типичную ошибку начинающих разработчиков. Пытаясь найти характеристику по нескольким свойствам, они передают список свойств и список значений в параметры запроса и пишут условие следующим образом:


ГДЕ
    ХарактеристикиНоменклатурыДополнительныеРеквизиты.Свойство В (&СписокСвойств)
    И ХарактеристикиНоменклатурыДополнительныеРеквизиты.Значение В (&СписокЗначений)

Давайте проанализируем, почему этот запрос вернет ложные результаты. Представим, что в нашей базе данных есть три характеристики со следующими наборами свойств:

Мы хотим найти характеристику, у которой Длина = 10, Ширина = 20 и Высота = 30. Мы передаем в параметры запроса:

При выполнении условия Свойство В (&СписокСвойств) И Значение В (&СписокЗначений) СУБД начнет проверять каждую строчку табличной части независимо. Для Характеристики №2 строка «Длина = 20» успешно пройдет проверку, так как свойство «Длина» есть в первом списке, а значение «20» (которое вообще-то относится к ширине в нашем поиске) есть во втором списке. В результате запрос выберет и свяжет строки, которые не имеют отношения к искомой комбинации, и вернет нам как Характеристику №1, так и Характеристику №2. Это классическая ошибка перекрестного сопоставления независимых списков.

Для корректного сопоставления нам необходимо связать каждое конкретное свойство с его конкретным значением. Рассмотрим рабочие варианты решения этой задачи.

Способ №1: Метод группировки и использование секции ИМЕЮЩИЕ (HAVING)

Это наиболее декларативный, красивый и часто используемый способ в платформе 1С. Суть метода заключается в следующем:

  1. Мы помещаем искомый набор пар «Свойство-Значение» во временную таблицу (или передаем как параметр типа ТаблицаЗначений).
  2. Соединяем эту таблицу с табличной частью дополнительных реквизитов характеристик по точному совпадению и свойства, и значения одновременно.
  3. Группируем результат по ссылке характеристики.
  4. С помощью секции ИМЕЮЩИЕ (аналог SQL-конструкции HAVING) отсекаем те характеристики, у которых количество совпавших свойств меньше, чем количество свойств в нашем поисковом запросе.

Давайте разберем практическую реализацию этого метода на встроенном языке 1С:


// Шаг 1. Подготовка исходной таблицы значений для поиска
ТаблицаПоиска = Новый ТаблицаЗначений;
ТаблицаПоиска.Колонки.Добавить("Свойство", Новый ОписаниеТипов("ПланВидовХарактеристикСсылка.ДополнительныеРеквизитыИСведения"));
ТаблицаПоиска.Колонки.Добавить("Значение"); // Тип колонки произвольный, зависит от типов значений свойств

// Заполняем таблицу искомыми свойствами и их значениями
НоваяСтрока = ТаблицаПоиска.Добавить();
НоваяСтрока.Свойство = СвойствоДлина;
НоваяСтрока.Значение = 10;

НоваяСтрока = ТаблицаПоиска.Добавить();
НоваяСтрока.Свойство = СвойствоШирина;
НоваяСтрока.Значение = 20;

НоваяСтрока = ТаблицаПоиска.Добавить();
НоваяСтрока.Свойство = СвойствоВысота;
НоваяСтрока.Значение = 30;

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

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

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

Пока Выборка.Следующий() Цикл
    // Выборка содержит ссылки на характеристики, которые полностью соответствуют нашему фильтру
    Сообщить("Найдена характеристика: " + Выборка.Ссылка);
КонецЦикла;

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

Решение проблемы «избыточных» свойств (Точное совпадение)

Описанный выше алгоритм решает задачу поиска по принципу «содержит как минимум указанные свойства». То есть, если у характеристики заполнены свойства Длина, Ширина, Высота и еще какое-то четвертое свойство Цвет, то данный запрос все равно вернет эту характеристику, поскольку первые три свойства совпали.

Но что делать, если перед нами стоит задача найти строго точное совпадение? Нам нужна характеристика, у которой заполнены только эти 3 свойства и больше никаких других дополнительных реквизитов нет. Для реализации такого строгого отбора нам нужно модифицировать условие ИМЕЮЩИЕ, добавив проверку общего количества реквизитов у найденного объекта:


|ИМЕЮЩИЕ
|	КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ.Свойство) = &КоличествоСвойств
|	И КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТЧ.Ссылка.ДополнительныеРеквизиты.Свойство) = &КоличествоСвойств

Обратите внимание, что обращение через точку к табличной части Ссылка.ДополнительныеРеквизиты.Свойство в условии может приводить к снижению производительности СУБД на очень больших объемах данных. Если ваша база содержит миллионы записей, рекомендуется предварительно вычислять общее количество реквизитов для каждой характеристики во временной таблице, а затем сопоставлять это число с количеством искомых свойств.

Способ №2: Динамическое построение запроса через INNER JOIN

Рассмотрим альтернативный подход, который часто применяется опытными разработчиками в высоконагруженных системах. Метод группировки с секцией ИМЕЮЩИЕ заставляет СУБД сначала соединить огромные массивы данных, а затем выполнить тяжелую операцию группировки (сортировку или построение хэш-таблиц в оперативной памяти).

Если мы построим запрос динамически, соединяя таблицу дополнительных реквизитов саму с собой столько раз, сколько свойств мы ищем, СУБД сможет использовать индексный поиск по каждому свойству последовательно. План выполнения такого запроса будет использовать алгоритм Nested Loops (вложенные циклы), каскадно сужая выборку на каждом шаге соединения.

Посмотрим на пример динамического построения такого запроса:


// Предположим, у нас есть массив структур со свойствами и значениями поиска
МассивУсловий = Новый Массив;
МассивУсловий.Добавить(Новый Структура("Свойство, Значение", СвойствоДлина, 10));
МассивУсловий.Добавить(Новый Структура("Свойство, Значение", СвойствоШирина, 20));
МассивУсловий.Добавить(Новый Структура("Свойство, Значение", СвойствоВысота, 30));

Запрос = Новый Запрос;

// Базовая часть запроса
ТекстЗапроса = 
    "ВЫБРАТЬ
    |	Т0.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Т0";

// Динамически добавляем соединения для каждого последующего свойства
Для Индекс = 1 По МассивУсловий.Количество() - 1 Цикл
    ТекстЗапроса = ТекстЗапроса + Символы.ПС +
        "	ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Т" + Индекс + "
        |		ПО Т0.Ссылка = Т" + Индекс + ".Ссылка
        |		И Т" + Индекс + ".Свойство = &Свойство" + Индекс + "
        |		И Т" + Индекс + ".Значение = &Значение" + Индекс;
КонецЦикла;

// Добавляем условие для самого первого (базового) свойства в секцию ГДЕ
ТекстЗапроса = ТекстЗапроса + Символы.ПС +
    "ГДЕ
    |	Т0.Свойство = &Свойство0
    |	И Т0.Значение = &Значение0";

Запрос.Текст = ТекстЗапроса;

// Устанавливаем параметры динамического запроса
Для Индекс = 0 По МассивУсловий.Количество() - 1 Цикл
    Запрос.УстановитьПараметр("Свойство" + Индекс, МассивУсловий[Индекс].Свойство);
    Запрос.УстановитьПараметр("Значение" + Индекс, МассивУсловий[Индекс].Значение);
КонецЦикла;

Для трех свойств итоговый текст запроса, который будет передан на СУБД, примет следующий вид:


ВЫБРАТЬ
    Т0.Ссылка КАК Ссылка
ИЗ
    Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Т0
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Т1
        ПО Т0.Ссылка = Т1.Ссылка
        И Т1.Свойство = &Свойство1
        И Т1.Значение = &Значение1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК Т2
        ПО Т0.Ссылка = Т2.Ссылка
        И Т2.Свойство = &Свойство2
        И Т2.Значение = &Значение2
ГДЕ
    Т0.Свойство = &Свойство0
    И Т0.Значение = &Значение0

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

Важнейший аспект оптимизации: Индексация в конфигураторе

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

Откройте конфигуратор и найдите справочник, по табличной части которого вы производите поиск (например, ХарактеристикиНоменклатуры). Перейдите на вкладку «Данные», откройте свойства табличной части ДополнительныеРеквизиты и выберите реквизит Свойство. Убедитесь, что для свойства Индексировать установлено значение Индексировать или Индексировать с дополнительным упорядочиванием.

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

Использование готовых механизмов БСП

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

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

Мы подробно разобрали различные аспекты поиска характеристик по их дополнительным реквизитам. Используйте метод группировки с ИМЕЮЩИЕ для простых и средних задач как наиболее понятный и легко поддерживаемый, а динамические соединения INNER JOIN применяйте для оптимизации критически важных участков кода и высоконагруженных систем.

← На главную