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

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

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

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

Почему конструкция ЗНАЧЕНИЕ() не работает для обычных элементов справочника?

Для начала выясним причину возникновения ошибки компиляции. Конструкция ЗНАЧЕНИЕ() — это специальная функция языка запросов 1С, которая вычисляется на этапе компиляции запроса платформой еще до его отправки в СУБД (MS SQL Server, PostgreSQL и др.).

Платформа может преобразовать текстовое представление объекта в его реальную ссылку в базе данных только в том случае, если информация об этом объекте жестко зафиксирована в метаданных конфигурации. К таким объектам относятся:

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

Анализ и критика популярных решений с форумов

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

Антипаттерн №1: Использование конструкции ВЫБОР КОГДА в секции ГДЕ

В сообщениях темы часто проскальзывает предложение использовать конструкцию вида:


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

Использование условных операторов ВЫБОР в секциях ГДЕ или в условиях соединения таблиц — это один из самых серьезных антипаттернов оптимизации запросов в 1С. Когда СУБД строит план выполнения такого запроса, она не может эффективно использовать индексы для поиска по полям, обернутым в логические конструкции. Вместо быстрого поиска по индексу (Index Seek) СУБД будет вынуждена выполнять полное сканирование таблицы (Table Scan / Clustered Index Scan). На базах данных большого объема это гарантированно приведет к длительным транзакционным блокировкам и зависанию системы.

Важное правило оптимизации: условия в секции ГДЕ должны быть максимально плоскими и простыми, например: ГДЕ Поле = &Параметр.

Антипаттерн №2: Поиск по жестко зашитому наименованию прямо в запросе

Другой популярный совет — искать элемент в запросе по его строковому представлению с помощью вложенных конструкций:


ГДЕ МаршрутноеЗаданиеДокументыРеализации.Реализация.Контрагент.КаналСбыта В (ВЫБРАТЬ Ссылка ИЗ Справочник.СегментыРынка ГДЕ Наименование = "Супермаркет")

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

Архитектурно правильные способы решения задачи

Рассмотрим по шагам надежные методы фильтрации данных по непредопределенному элементу справочника.

Способ 1. Передача элемента через параметры запроса (Классический метод)

Этот метод основан на том, что мы находим нужную ссылку во встроенном коде 1С и передаем ее в запрос в качестве параметра. Посмотрим на пример кода:


// 1. Находим нужный элемент справочника во встроенном коде
СсылкаСупермаркет = Справочники.СегментыРынка.НайтиПоНаименованию("Супермаркет");

Если СсылкаСупермаркет.Пустая() Тогда
    ВызватьИсключение "В справочнике 'Сегменты рынка' не найден элемент 'Супермаркет'!";
КонецЕсли;

// 2. Создаем и настраиваем запрос
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	МаршрутноеЗаданиеДокументыРеализации.Ссылка КАК МаршрутныйЛист
    |ИЗ
    |	Документ.МаршрутноеЗадание.ДокументыРеализации КАК МаршрутноеЗаданиеДокументыРеализации
    |ГДЕ
    |	МаршрутноеЗаданиеДокументыРеализации.Реализация.Контрагент.КаналСбыта = &КаналСбыта";

// 3. Устанавливаем параметр
Запрос.УстановитьПараметр("КаналСбыта", СсылкаСупермаркет);

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

Этот способ решает главную проблему производительности СУБД: в физический SQL-запрос передается готовая ссылка (уникальный идентификатор), благодаря чему СУБД строит оптимальный план выполнения и использует индекс по полю КаналСбыта.

Способ 2. Использование уникального идентификатора (UUID / GUID)

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

Выясним уникальный идентификатор нужного нам элемента с помощью специальной обработки (или программно) и зафиксируем его в коде:


// Получаем ссылку по известному уникальному идентификатору (GUID)
Идентификатор = Новый УникальныйИдентификатор("7d2bc51e-9a1b-11ed-a8fc-00155d013e02");
СсылкаСупермаркет = Справочники.СегментыРынка.ПолучитьСсылку(Идентификатор);

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

Запрос.УстановитьПараметр("КаналСбыта", СсылкаСупермаркет);
РезультатЗапроса = Запрос.Выполнить();

Этот подход гарантирует стабильность работы программы при любых изменениях наименования или кода элемента пользователями. Однако стоит помнить, что при переносе базы данных «с нуля» через xml-выгрузку без сохранения идентификаторов GUID может измениться, поэтому данный способ требует контроля за уникальными идентификаторами при миграции данных между базами.

Способ 3. Вынесение ссылки в константы или настройки системы (Архитектурно лучший подход)

Для крупных корпоративных решений «зашивание» каких-либо строковых имен или жестких GUID-ов непосредственно в код программы считается плохим тоном разработки. Наиболее гибким решением является вынесение выбора сегмента на уровень административных настроек системы.

Разберем по шагам реализацию этого подхода:

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

// Считываем ссылку из константы
СегментИзНастроек = Константы.ОсновнойСегментСупермаркет.Получить();

Если Не ЗначениеЗаполнено(СегментИзНастроек) Тогда
    ВызватьИсключение "В настройках системы не заполнен основной сегмент рынка для супермаркетов!";
КонецЕсли;

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	МаршрутноеЗаданиеДокументыРеализации.Ссылка КАК МаршрутныйЛист
    |ИЗ
    |	Документ.МаршрутноеЗадание.ДокументыРеализации КАК МаршрутноеЗаданиеДокументыРеализации
    |ГДЕ
    |	МаршрутноеЗаданиеДокументыРеализации.Реализация.Контрагент.КаналСбыта = &КаналСбыта";

Запрос.УстановитьПараметр("КаналСбыта", СегментИзНастроек);
РезультатЗапроса = Запрос.Выполнить();

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

Способ 4. Создание предопределенного элемента в метаданных

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

  1. Откройте конфигуратор, найдите справочник СегментыРынка.
  2. Перейдите на вкладку Прочие и нажмите кнопку Предопределенные.
  3. Добавьте новый предопределенный элемент с именем Супермаркет.

После обновления конфигурации базы данных вы сможете использовать конструкцию ЗНАЧЕНИЕ() прямо в тексте запроса без передачи параметров:


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

Программная привязка предопределенных элементов в режиме «1С:Предприятие»

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

Рассмотрим пример кода:


// Получаем ссылку на существующий элемент, созданный пользователем
СсылкаСупермаркет = Справочники.СегментыРынка.НайтиПоНаименованию("Супермаркет");

Если Не СсылкаСупермаркет.Пустая() Тогда
    // Устанавливаем связь существующего элемента с предопределенным именем "Супермаркет"
    Справочники.СегментыРынка.УстановитьСвязьПредопределенного("Супермаркет", СсылкаСупермаркет);
    Сообщить("Связь предопределенного элемента успешно установлена!");
Иначе
    Сообщить("Элемент 'Супермаркет' не найден в базе данных для сопоставления.");
КонецЕсли;

После выполнения данного кода система свяжет UUID существующего элемента с именем предопределенного элемента метаданных. Конструкция ЗНАЧЕНИЕ(Справочник.СегментыРынка.Супермаркет) начнет корректно ссылаться на данный элемент без заведения новых записей в информационной базе.

Важный нюанс оптимизации: Проблема неявных соединений в 1С

Обратим внимание на конструкцию в тексте исходного запроса:

МаршрутноеЗаданиеДокументыРеализации.Реализация.Контрагент.КаналСбыта

Использование обращения к реквизитам через точку (разыменование полей) приводит к тому, что платформа 1С на уровне СУБД генерирует неявные левые соединения (LEFT JOIN). В данном случае для проверки одного условия СУБД выполнит три левых соединения:

  1. К таблице документов РеализацияТоваровУслуг.
  2. К таблице справочника Контрагенты.
  3. К таблице справочника СегментыРынка.

Если таблицы содержат миллионы записей, производительность такого запроса резко упадет. Для оптимизации рекомендуется переписывать такие участки с использованием явных соединений (ВНУТРЕННЕЕ СОЕДИНЕНИЕ или ЛЕВОЕ СОЕДИНЕНИЕ) только с необходимыми таблицами. Посмотрим на оптимизированный пример:


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

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

Резюме лучших практик

Подведем итог нашему анализу:

← На главную