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

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

В процессе разработки в среде «1С:Предприятие 8» мы регулярно сталкиваемся с необходимостью поиска объектов по их дополнительным реквизитам и сведениям (механизм БСП). Типичный пример — поиск конкретной Характеристики номенклатуры по набору её свойств (например, цвет, размер, материал, сезонность). Поскольку эти свойства хранятся в подчиненной табличной части объекта, простая задача поиска превращается в архитектурный вызов.

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

Способ 1. Классическое левое соединение (для фиксированного набора свойств)

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

Давайте посмотрим на пример такого запроса. Предположим, нам нужно найти характеристику конкретной номенклатуры, у которой «Цвет» равен определенному значению, а «Размер» — другому значению.


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

Преимущества метода:

  1. Простота понимания и отладки в консоли запросов.
  2. Хорошая производительность при небольшом количестве соединений (до 3-4 свойств) и установленном отборе по владельцу (номенклатуре).

Недостатки метода:

  1. Запрос статичен. Если у вас 10 возможных свойств, и в разных строках табличной части документа заполнено разное их количество (например, где-то только цвет, где-то цвет и размер), вам придется динамически формировать текст запроса, добавляя или убирая левые соединения.
  2. При увеличении количества соединяемых таблиц (более 5-6) производительность СУБД начинает лавинообразно снижаться из-за построения сложных планов запросов.

Способ 2. Элегантное решение через временную таблицу пар «Свойство-Значение» (Рекомендуется)

Что делать, если нам нужно обработать целую табличную часть документа за один запрос, избегая циклов, а количество заполненных свойств для каждой строки непредсказуемо? Здесь нам на помощь приходит математический метод пересечения множеств.

Некоторые разработчики пытаются решить это простым условием В (&МассивСвойств) и В (&МассивЗначений), однако это приводит к «ловушке соответствия». Если у одной характеристики совпадет Свойство 1 со значением от Свойства 2, система вернет ложноположительный результат. Нам необходимо проверять именно пары совпадений.

Рассмотрим детальный пошаговый алгоритм правильного решения:

  1. Создаем на входе Таблицу Значений (ТЗ), содержащую две колонки: Свойство и Значение. Наполняем её только теми свойствами, которые реально заполнены и по которым нужно вести поиск.
  2. Передаем эту ТЗ в качестве параметра в запрос и помещаем во временную таблицу.
  3. Соединяем табличную часть характеристик с этой временной таблицей одновременно по двум полям (Свойство и Значение).
  4. Группируем результат по характеристикам и отсекаем те, у которых количество совпадений меньше, чем количество искомых свойств.

Посмотрим на пример реализации данного запроса в коде:


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

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

Если ЗначениеЗаполнено(ИскомыйРазмер) Тогда
    НоваяСтрока = ТЗ_Параметры.Добавить();
    НоваяСтрока.Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Размер");
    НоваяСтрока.Значение = ИскомыйРазмер;
КонецЕсли;

КоличествоИскомыхСвойств = ТЗ_Параметры.Количество();

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

Запрос.УстановитьПараметр("ТЗ_Параметры", ТЗ_Параметры);
Запрос.УстановитьПараметр("Номенклатура", ВыбраннаяНоменклатура);
Запрос.УстановитьПараметр("КоличествоИскомыхСвойств", КоличествоИскомыхСвойств);

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

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

Способ 3. Динамическое построение через «Схему запроса»

Если вам все же необходимо строить левые соединения динамически (например, для вывода свойств в виде отдельных колонок в динамическом списке или отчете), откажитесь от ручной склейки текста запроса строковыми методами. Это усложняет чтение кода и часто приводит к синтаксическим ошибкам.

Начиная с платформы 8.3, рекомендуется использовать объект СхемаЗапроса. Он позволяет программно работать со структурой запроса как с объектной моделью.

Проанализируем пример использования этого механизма:


Схема = Новый СхемаЗапроса;
Схема.УстановитьТекстЗапроса(
    "ВЫБРАТЬ
    |	Характеристики.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.ХарактеристикиНоменклатуры КАК Характеристики
    |ГДЕ
    |	Характеристики.Владелец = &Владелец");

Пакет0 = Схема.ПакетЗапросов[0];
ТаблицаХарактеристики = Пакет0.Источники[0];

// Динамически в цикле добавляем левые соединения под каждое заполненное свойство
Для Каждого СтрокаСвойства Из СписокВыбранныхСвойств Цикл
    
    ИмяСвойства = СтрокаСвойства.ИмяБезСпецСимволов; // Подготавливаем уникальное имя для таблицы
    
    НовыйИсточник = Пакет0.Источники.Добавить("Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты", ИмяСвойства);
    
    // Описываем связь
    Связь = Пакет0.СвязиТаблиц.Добавить();
    Связь.ЛевоеВыражение = Новый ВыражениеСхемыЗапроса("Характеристики.Ссылка");
    Связь.ПравоеВыражение = Новый ВыражениеСхемыЗапроса(ИмяСвойства + ".Ссылка");
    Связь.ТипСоединения = ТипСоединенияСхемыЗапроса.Левое;
    
    // Добавляем условие по конкретному свойству в связь
    СвязьУсловие = Пакет0.СвязиТаблиц.Добавить();
    СвязьУсловие.ЛевоеВыражение = Новый ВыражениеСхемыЗапроса(ИмяСвойства + ".Свойство");
    СвязьУсловие.ПравоеВыражение = Новый ВыражениеСхемыЗапроса("&Свойство_" + ИмяСвойства);
    
    // Выводим значение свойства в колонки выборки
    Пакет0.Колонки.Добавить(ИмяСвойства + ".Значение", "Значение_" + ИмяСвойства);
КонецЦикла;

// Получаем готовый текст запроса
ТекстЗапроса = Схема.ПолучитьТекстЗапроса();

Этот подход гарантирует чистоту кода, отсутствие синтаксических ошибок в ключевых словах SQL и автоматическое форматирование результирующего текста платформой.

Способ 4. Использование механизма «Характеристики» в СКД

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

Выясним, как его правильно настроить:

  1. В конструкторе схемы компоновки данных перейдите на вкладку «Характеристики».
  2. Добавьте новую строку и укажите:
    • Источник видов характеристик: План видов характеристик ДополнительныеРеквизитыИСведения (или табличная часть / регистр, где они регистрируются).
    • Источник значений характеристик: Табличная часть ДополнительныеРеквизиты нужного вам справочника.
  3. Укажите поля связи (ключи связи по объекту, свойству и значению).

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

Важный нюанс: Проблема производительности СУБД

Разработчикам высоконагруженных систем важно помнить о физической структуре хранения данных дополнительных реквизитов. Поле Значение в табличных частях дополнительных реквизитов имеет составной тип данных (оно может хранить числа, строки, ссылки на различные справочники вроде «Значения свойств объектов» и т.д.).

СУБД (будь то MS SQL Server или PostgreSQL) крайне неэффективно индексирует поля составного типа. Поиск по значению доп. реквизита при объеме базы в сотни тысяч и миллионы записей неизбежно приведет к полному сканированию таблиц (Table Scan / Index Scan) и сильным зависаниям системы.

Рекомендации по оптимизации:

← На главную