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

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

При разработке отчетов и обработок часто возникает необходимость извлечь значения определенных дополнительных реквизитов или сведений для нескольких объектов (например, получить руководителей для ряда подразделений). Нередко разработчики сталкиваются с трудностями при сопоставлении полученных результатов, допускают ошибки при обращении к строкам по индексу или создают избыточную нагрузку на базу данных множественными точечными вызовами. Разберем типичные ошибки и рассмотрим правильные варианты решения этой задачи.

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


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

Подразделения = Новый Массив;
Подразделения.Добавить(Параметры.Подразделение);
Подразделения.Добавить(Справочники.ПодразделенияОрганизаций.НайтиПоКоду("000000009")); // Отдел кадров
Подразделения.Добавить(Справочники.ПодразделенияОрганизаций.НайтиПоКоду("000000011")); // Отдел ОЭН

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

РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Параметры.НачПодразделения = РезультатЗапроса[0].Значение;
Параметры.НачОК            = РезультатЗапроса[1].Значение;
Параметры.НачОЭН           = РезультатЗапроса[2].Значение;

Почему обращение по фиксированным индексам — грубая ошибка?

В приведенном коде скрыты сразу две критические проблемы:

  1. Недетерминированность порядка строк: Реляционная СУБД не гарантирует порядок выборки записей, если в запросе отсутствует инструкция УПОРЯДОЧИТЬ ПО. Порядок строк в таблице результата ни в коем случае не повторяет порядок элементов в переданном массиве &Ссылка. В итоге РезультатЗапроса[0] вполне может содержать начальника отдела кадров вместо начальника основного подразделения.
  2. Пустые значения в табличной части: В типовых конфигурациях 1С дополнительные реквизиты хранятся в табличной части ДополнительныеРеквизиты. Если у какого-то подразделения реквизит не был заполнен, строки для него в табличной части физически не существует. Запрос вернет не три строки, а две или одну. При попытке выполнить РезультатЗапроса[2] платформа вызовет критическую ошибку «Индекс находится за границами массива».
  3. Лишние обращения к базе данных: Вызовы НайтиПоКоду и НайтиПоРеквизиту выполняют неявные одиночные запросы к СУБД перед выполнением основного кода.

Вариант 1. Запрос с разворотом значений в одну строку через агрегацию

Если нам гарантированно требуется получить результат одной строкой с понятными именованными колонками, оптимально использовать конструкцию с агрегатной функцией МАКСИМУМ и оператором ВЫБОР. Все условия по кодам переносятся прямо в тело запроса.

Посмотрим на пример реализации:


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

Запрос.УстановитьПараметр("Подразделение", Параметры.Подразделение);
Запрос.УстановитьПараметр("ИмяСвойства", "НачальникПодразделения");

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

Такой подход обладает важными преимуществами:

  1. Выполняется ровно один запрос к СУБД без предварительных поисков ссылок.
  2. Результат всегда возвращает ровно одну строку, исключая падение по индексу.
  3. Если у подразделения не задан начальник, в соответствующей колонке вернется NULL или Неопределено.

Вариант 2. Выборка пар «Ссылка – Значение» с заполнением структуры или соответствия

Если список объектов динамический и заранее неизвестно точное количество подразделений, правильным решением является выборка ссылки вместе со значением свойства, с последующей загрузкой в Соответствие.

Разберем по шагам данный алгоритм:


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

Запрос.УстановитьПараметр("МассивПодразделений", Подразделения);
Запрос.УстановитьПараметр("ИмяСвойства", "НачальникПодразделения");

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

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

// Получение значений:
Параметры.НачПодразделения = СоответствиеНачальников[Параметры.Подразделение];

При таком подходе порядок следования записей в результате запроса не имеет значения — мы безопасно получаем руководителя по ссылке подразделения через ключ Соответствия.

Вариант 3. Использование стандартных функций БСП

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

Для единичного получения значения можно вызвать функцию:


Значение = УправлениеСвойствами.ЗначениеСвойства(Подразделение, "НачальникПодразделения");

Важно учитывать: вызов УправлениеСвойствами.ЗначениеСвойства внутри циклов или многократно подряд для разных объектов приводит к возникновению проблемы N+1 запросов к базе данных. Если объектов несколько, предпочтительно использовать пакетные запросы (как в Вариантах 1 и 2) или специализированные методы БСП для группового чтения свойств наборов данных.

← На главную