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

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

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

Анализ исходной ошибки: почему не работает простой обход в цикле?

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


Пока Результат.Следующий() Цикл        
    ДатаРегистрацииОрганизации = Результат.Значение;
    НомерРегистрации = Результат.Значение;
    СерияРегистрации = Результат.Значение;
КонецЦикла;

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

  1. На первой итерации (например, считалась «Дата регистрации») все три переменные принимают значение даты.
  2. На второй итерации (считался «Номер регистрации») все три переменные перезаписываются значением номера.
  3. На третьей итерации (считалась «Серия регистрации») все три переменные перезаписываются значением серии.

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

Способ №1: Извлечение свойств с помощью левых соединений в одном запросе

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

Давайте разберем структуру такого запроса на примере получения даты, номера и серии регистрации организации. Обратите внимание, что условие отбора по конкретному свойству накладывается строго в секции ПО связи левого соединения, а не в общем условии ГДЕ. Это предотвратит пропажу всей строки организации из результата, если какой-то один из реквизитов не заполнен.


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

// Установка параметров запроса
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОрганизацию);
Запрос.УстановитьПараметр("СвойствоДата",  СвойствоДатаРегистрации);
Запрос.УстановитьПараметр("СвойствоНомер", СвойствоНомерРегистрации);
Запрос.УстановитьПараметр("СвойствоСерия", СвойствоСерияРегистрации);

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

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

Способ №2: Однопроходный запрос с последующим разбором в коде

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

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


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

СписокСвойств = Новый Массив;
СписокСвойств.Добавить(СвойствоДатаРегистрации);
СписокСвойств.Добавить(СвойствоНомерРегистрации);
СписокСвойств.Добавить(СвойствоСерияРегистрации);

Запрос.УстановитьПараметр("Ссылка", СсылкаНаОрганизацию);
Запрос.УстановитьПараметр("СписокНужныхСвойств", СписокСвойств);

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

// Инициализируем переменные дефолтными значениями
ДатаРегистрацииОрганизации = '00010101';
НомерРегистрации           = "";
СерияРегистрации           = "";

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

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

Способ №3: Лучшая практика с использованием механизмов БСП

Современные конфигурации 1С (включая «Бухгалтерию для Казахстана» редакции 3.0) построены на базе Библиотеки стандартных подсистем (БСП). Подсистема «Свойства» предоставляет готовый программный интерфейс для работы с дополнительными реквизитами и сведениями. Использование общего модуля БСП УправлениеСвойствами избавляет разработчика от необходимости писать ручные запросы к табличным частям. Для быстрого поиска функций и изучения кода подсистем пригодится справочник по методам БСП с примерами использования.

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

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


// 1. Формируем массив свойств, которые нам интересны
МассивНужныхСвойств = Новый Массив;
МассивНужныхСвойств.Добавить(СвойствоДатаРегистрации);
МассивНужныхСвойств.Добавить(СвойствоНомерРегистрации);
МассивНужныхСвойств.Добавить(СвойствоСерияРегистрации);

// 2. Вызываем стандартную функцию БСП
// Параметры: Ссылка на объект, ПолучатьДопРеквизиты (Истина), ПолучатьДопСведения (Ложь), Массив нужных свойств
ТаблицаСвойств = УправлениеСвойствами.ПолучитьЗначенияСвойств(СсылкаНаОрганизацию, Истина, Ложь, МассивНужныхСвойств);

// 3. Быстро находим нужные строки в полученной таблице значений
СтрокаДата = ТаблицаСвойств.Найти(СвойствоДатаРегистрации, "Свойство");
ДатаРегистрацииОрганизации = ?(СтрокаДата <> Неопределено, СтрокаДата.Значение, '00010101');

СтрокаНомер = ТаблицаСвойств.Найти(СвойствоНомерРегистрации, "Свойство");
НомерРегистрации = ?(СтрокаНомер <> Неопределено, СтрокаНомер.Значение, "");

СтрокаСерия = ТаблицаСвойств.Найти(СвойствоСерияРегистрации, "Свойство");
СерияРегистрации = ?(СтрокаСерия <> Неопределено, СтрокаСерия.Значение, "");

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

Рекомендация по поиску свойств: откажитесь от поиска по наименованию!

В сообщениях на форуме часто предлагают искать ссылки на элементы плана видов характеристик ДополнительныеРеквизитыИСведения с помощью метода НайтиПоНаименованию(). Мы настоятельно рекомендуем избегать этого приема в промышленной разработке.

Наименование дополнительного реквизита — это текстовое поле, которое отображается на форме пользователям. Любой пользователь с правами администратора может в любой момент изменить текст "Дата регистрации (Организации)" на "Дата гос. регистрации организации". После этого ваш код, завязанный на поиск по имени, мгновенно перестанет работать, что приведет к скрытым ошибкам в системе.

Как делать правильно: в БСП у каждого дополнительного реквизита есть специальный скрытый реквизит Имя (уникальный идентификатор разработчика). Вы можете задать его один раз при создании свойства. Это имя неизменно и недоступно для простого редактирования обычными пользователями. Искать свойства в коде следует именно по нему:


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

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

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

← На главную