При разработке отчетов и обработок часто возникает необходимость извлечь значения определенных дополнительных реквизитов или сведений для нескольких объектов (например, получить руководителей для ряда подразделений). Нередко разработчики сталкиваются с трудностями при сопоставлении полученных результатов, допускают ошибки при обращении к строкам по индексу или создают избыточную нагрузку на базу данных множественными точечными вызовами. Разберем типичные ошибки и рассмотрим правильные варианты решения этой задачи.
Проанализируем типовую ситуацию, когда требуется получить начальников для трех подразделений: выбранного пользователем, отдела кадров и планово-экономического отдела. Исходный код разработчика выглядел следующим образом:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПодразделенияОрганизацийДополнительныеРеквизиты.Значение КАК Значение
|ИЗ
| Справочник.ПодразделенияОрганизаций.ДополнительныеРеквизиты КАК ПодразделенияОрганизацийДополнительныеРеквизиты
|ГДЕ
| ПодразделенияОрганизацийДополнительныеРеквизиты.Ссылка В(&Ссылка)
| И ПодразделенияОрганизацийДополнительныеРеквизиты.Свойство = &Свойство";
Подразделения = Новый Массив;
Подразделения.Добавить(Параметры.Подразделение);
Подразделения.Добавить(Справочники.ПодразделенияОрганизаций.НайтиПоКоду("000000009")); // Отдел кадров
Подразделения.Добавить(Справочники.ПодразделенияОрганизаций.НайтиПоКоду("000000011")); // Отдел ОЭН
Запрос.УстановитьПараметр("Ссылка", Подразделения);
Запрос.УстановитьПараметр("Свойство", ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоРеквизиту("Имя", "НачальникПодразделения"));
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Параметры.НачПодразделения = РезультатЗапроса[0].Значение;
Параметры.НачОК = РезультатЗапроса[1].Значение;
Параметры.НачОЭН = РезультатЗапроса[2].Значение;
В приведенном коде скрыты сразу две критические проблемы:
УПОРЯДОЧИТЬ ПО. Порядок строк в таблице результата ни в коем случае не повторяет порядок элементов в переданном массиве &Ссылка. В итоге РезультатЗапроса[0] вполне может содержать начальника отдела кадров вместо начальника основного подразделения.ДополнительныеРеквизиты. Если у какого-то подразделения реквизит не был заполнен, строки для него в табличной части физически не существует. Запрос вернет не три строки, а две или одну. При попытке выполнить РезультатЗапроса[2] платформа вызовет критическую ошибку «Индекс находится за границами массива».НайтиПоКоду и НайтиПоРеквизиту выполняют неявные одиночные запросы к СУБД перед выполнением основного кода.Если нам гарантированно требуется получить результат одной строкой с понятными именованными колонками, оптимально использовать конструкцию с агрегатной функцией МАКСИМУМ и оператором ВЫБОР. Все условия по кодам переносятся прямо в тело запроса.
Посмотрим на пример реализации:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МАКСИМУМ(ВЫБОР
| КОГДА ДопРеквизиты.Ссылка = &Подразделение
| ТОГДА ДопРеквизиты.Значение
| ИНАЧЕ НЕОПРЕДЕЛЕНО
| КОНЕЦ) КАК НачПодразделения,
| МАКСИМУМ(ВЫБОР
| КОГДА ДопРеквизиты.Ссылка.Код = ""000000009""
| ТОГДА ДопРеквизиты.Значение
| ИНАЧЕ НЕОПРЕДЕЛЕНО
| КОНЕЦ) КАК НачОК,
| МАКСИМУМ(ВЫБОР
| КОГДА ДопРеквизиты.Ссылка.Код = ""000000011""
| ТОГДА ДопРеквизиты.Значение
| ИНАЧЕ НЕОПРЕДЕЛЕНО
| КОНЕЦ) КАК НачОЭН
|ИЗ
| Справочник.ПодразделенияОрганизаций.ДополнительныеРеквизиты КАК ДопРеквизиты
|ГДЕ
| (ДопРеквизиты.Ссылка = &Подразделение
| ИЛИ ДопРеквизиты.Ссылка.Код В (""000000009"", ""000000011""))
| И ДопРеквизиты.Свойство.Имя = &ИмяСвойства";
Запрос.УстановитьПараметр("Подразделение", Параметры.Подразделение);
Запрос.УстановитьПараметр("ИмяСвойства", "НачальникПодразделения");
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Параметры.НачПодразделения = Выборка.НачПодразделения;
Параметры.НачОК = Выборка.НачОК;
Параметры.НачОЭН = Выборка.НачОЭН;
КонецЕсли;
Такой подход обладает важными преимуществами:
NULL или Неопределено.Если список объектов динамический и заранее неизвестно точное количество подразделений, правильным решением является выборка ссылки вместе со значением свойства, с последующей загрузкой в Соответствие.
Разберем по шагам данный алгоритм:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДопРеквизиты.Ссылка КАК Подразделение,
| ДопРеквизиты.Значение КАК Значение
|ИЗ
| Справочник.ПодразделенияОрганизаций.ДополнительныеРеквизиты КАК ДопРеквизиты
|ГДЕ
| ДопРеквизиты.Ссылка В(&МассивПодразделений)
| И ДопРеквизиты.Свойство.Имя = &ИмяСвойства";
Запрос.УстановитьПараметр("МассивПодразделений", Подразделения);
Запрос.УстановитьПараметр("ИмяСвойства", "НачальникПодразделения");
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
// Заполняем соответствие для быстрого и безопасного поиска
СоответствиеНачальников = Новый Соответствие;
Для Каждого Стр Из ТаблицаРезультата Цикл
СоответствиеНачальников.Вставить(Стр.Подразделение, Стр.Значение);
КонецЦикла;
// Получение значений:
Параметры.НачПодразделения = СоответствиеНачальников[Параметры.Подразделение];
При таком подходе порядок следования записей в результате запроса не имеет значения — мы безопасно получаем руководителя по ссылке подразделения через ключ Соответствия.
В конфигурациях, использующих Библиотеку Стандартных Подсистем (БСП), для работы со свойствами предусмотрен специальный общий модуль УправлениеСвойствами.
Для единичного получения значения можно вызвать функцию:
Значение = УправлениеСвойствами.ЗначениеСвойства(Подразделение, "НачальникПодразделения");
Важно учитывать: вызов УправлениеСвойствами.ЗначениеСвойства внутри циклов или многократно подряд для разных объектов приводит к возникновению проблемы N+1 запросов к базе данных. Если объектов несколько, предпочтительно использовать пакетные запросы (как в Вариантах 1 и 2) или специализированные методы БСП для группового чтения свойств наборов данных.