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