Почему не работает выборка из справочника с отбором по двум параметрам в 1С и как это исправить?

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

Разработчики в среде «1С:Предприятие» часто сталкиваются со стандартными механизмами выборки данных. Начинающие специалисты нередко пытаются использовать метод Выбрать() менеджера справочника для получения отфильтрованных записей, передавая в структуру отбора сразу несколько параметров. Однако такой подход быстро приводит к ошибкам выполнения программы или неверным результатам.

В этой статье мы подробно проанализируем, почему код автора из примера не работает, разберем ключевые ограничения платформы «1С:Предприятие» при работе с выборками и рассмотрим наиболее эффективные способы решения этой задачи.

Анализ исходного кода и выявление ошибок

Для начала давайте вернемся к коду, который вызвал затруднения у автора темы:


Отбор = Новый Структура;
Отбор.Вставить("Характеристика", Характеристика);
Отбор.Вставить("Наименование", ВыборкаДетальныеЗаписи.номенклатура.наименование); 

НайденаяСпецификация = Справочники.СпецификацииНоменклатуры.Выбрать(,, Отбор);

На первый взгляд код выглядит логичным: создается структура отбора, в нее добавляются два свойства (Характеристика и Наименование), после чего вызывается метод Выбрать(). Однако данный код не будет работать по трем фундаментальным причинам, заложенным в архитектуру платформы 1С.

Причина №1: Жесткое ограничение метода Выбрать() на количество параметров отбора

Рассмотрим официальные требования платформы «1С:Предприятие» к методу Выбрать() для справочников. Согласно синтакс-помощнику, данный метод имеет следующие параметры:

Выбрать(<Родитель>, <Владелец>, <Отбор>, <Порядок>)

Обратим внимание на параметр Отбор. В него передается объект типа Структура. Ключевое ограничение платформы заключается в следующем: для метода выборки справочника структура отбора может содержать только один элемент!

Если мы попытаемся вставить в структуру более одного ключа (в нашем примере это Характеристика и Наименование), то платформа выдаст ошибку выполнения: «Недопустимое значение параметра». Наложение фильтра по двум произвольным реквизитам напрямую через метод Выбрать() невозможно.

Причина №2: Игнорирование подчиненности справочника (поле Владелец)

Справочник СпецификацииНоменклатуры по своей логике почти всегда является подчиненным по отношению к справочнику Номенклатура. Это означает, что каждая спецификация жестко привязана к конкретной номенклатурной позиции.

В исходном коде автор пытается сделать отбор по наименованию номенклатуры: ВыборкаДетальныеЗаписи.номенклатура.наименование. Это концептуально неверно по двум причинам:

  1. Отбор нужно делать не по строковому представлению (наименованию), а по ссылке на сам элемент-владелец.
  2. Для фильтрации по владельцу у метода Выбрать() существует специальный выделенный второй параметр — <Владелец>. Передавать владельца внутрь структуры Отбор нельзя.

Причина №3: Требования к индексации полей

Даже если бы мы оставили в структуре Отбор только один параметр (например, Характеристика), выборка все равно могла бы завершиться ошибкой. Для использования реквизита в качестве фильтра в методе Выбрать() для этого реквизита в конфигураторе обязательно должно быть включено свойство «Индексировать» (или «Индексировать с доп. упорядочиванием»).

Если реквизит Характеристика не проиндексирован разработчиком в метаданных, платформа заблокирует выполнение кода с ошибкой.

---

Вариант решения №1: Исправление метода Выбрать()

Если мы все же хотим использовать стандартный метод выборки, нам необходимо привести код в соответствие с правилами платформы. Мы можем использовать параметр Владелец для отбора по номенклатуре, а в структуру Отбор передать единственный оставшийся параметр — проиндексированную характеристику:


// Создаем отбор только по ОДНОМУ дополнительному полю
Отбор = Новый Структура;
Отбор.Вставить("Характеристика", Характеристика); // Реквизит должен быть проиндексирован в конфигураторе!

// Владельца передаем вторым параметром метода Выбрать()
ВладелецСсылка = ВыборкаДетальныеЗаписи.Номенклатура;

НайденаяСпецификацияВыборка = Справочники.СпецификацииНоменклатуры.Выбрать(, ВладелецСсылка, Отбор);

Если НайденаяСпецификацияВыборка.Следующий() Тогда
    НайденаяСпецификация = НайденаяСпецификацияВыборка.Ссылка;
КонецЕсли;

Этот код будет работоспособен, однако он имеет существенный недостаток: мы по-прежнему не можем наложить дополнительный фильтр по наименованию спецификации на уровне СУБД. Нам придется дописывать обход выборки в цикле и сравнивать наименования вручную в коде, что крайне неэффективно с точки зрения производительности.

---

Вариант решения №2: Использование Запроса (Рекомендуемый способ)

Как совершенно справедливо отметили участники обсуждения на форуме, для решения подобных задач правильным и наиболее профессиональным подходом является использование объекта Запрос.

Преимущества использования запроса перед методом Выбрать():

Рассмотрим детальный пример реализации нашей задачи с помощью механизма запросов:


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

// Передаем параметры в запрос
Запрос.УстановитьПараметр("Владелец", ВыборкаДетальныеЗаписи.Номенклатура);
Запрос.УстановитьПараметр("Характеристика", Характеристика);
// Очищаем наименование от лишних пробелов для точного поиска
Запрос.УстановитьПараметр("Наименование", СокрЛП(ВыборкаДетальныеЗаписи.Номенклатура.Наименование));

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

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

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

← На главную