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

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

При разработке отчетов и обработок в 1С программисты часто сталкиваются с необходимостью получения значений дополнительных реквизитов и сведений для различных объектов (например, для справочника ОсновныеСредства или Номенклатура). Механизм дополнительных реквизитов реализован в Библиотеке стандартных подсистем (БСП) в виде табличной части объекта. Поскольку данные хранятся в виде строк "Свойство - Значение", их получение в запросе требует особого подхода.

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

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

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

Ошибка 1. Перезапись параметров в коде 1С

Обратите внимание на то, как устанавливаются параметры запроса перед его выполнением:


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

Параметры запроса в платформе 1С хранятся в виде структуры (коллекции "Ключ-Значение"), где имя параметра является уникальным ключом. Когда мы дважды вызываем метод УстановитьПараметр с одним и тем же именем "Свойство", второе значение («Накопленная амортизация») просто перезаписывает первое («Первоначальная стоимость»). В результате внутри запроса под параметром &Свойство всегда передается только второе значение.

Ошибка 2. Логическое противоречие в условии соединения таблиц

Даже когда разработчик попытался исправить ситуацию и разделил параметры на &Свойство1 и &Свойство2, он допустил логическую ошибку в связях левого соединения:


ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ОсновныеСредства.ДополнительныеРеквизиты КАК ОсновныеСредстваДополнительныеРеквизиты
ПО ...
И (ОсновныеСредстваДополнительныеРеквизиты.Свойство = &Свойство1)
И (ОсновныеСредстваДополнительныеРеквизиты.Свойство = &Свойство2)

Давайте разберемся, как хранятся дополнительные реквизиты в базе данных. Каждая строка табличной части ДополнительныеРеквизиты содержит только одно свойство и его значение. Физически одна и та же строка не может одновременно содержать и свойство «Первоначальная стоимость», и свойство «Накопленная амортизация». Связывая таблицы через логическое И для одного и того же поля Свойство, мы требуем от СУБД невозможного. В результате такое соединение всегда будет возвращать NULL.

Ошибка 3. Катастрофическая потеря производительности (Кросс-соединение)

В самом конце секции ИЗ исходного запроса допущена грубейшая ошибка:


|    РегистрСведений.ПервоначальныеСведенияОСБухгалтерскийУчет КАК ПервоначальныеСведенияОСБухгалтерскийУчет,
|    РегистрБухгалтерии.Хозрасчетный.Остатки(&ДатаСведений, , , ) КАК ХозрасчетныйОстаткиАмортизация

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


Решение №1: Использование нескольких левых соединений (Простой способ)

Самый простой и понятный способ получить несколько различных дополнительных реквизитов для одного объекта — присоединить табличную часть ДополнительныеРеквизиты столько раз, сколько реквизитов нам требуется. Каждому соединению необходимо присвоить свой уникальный псевдоним (алиас).

Давайте разберем этот способ по шагам.

Шаг 1. Настройка параметров в модуле 1С

Создадим уникальные параметры для каждого свойства:


СвойстваОС = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения;

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

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

Шаг 2. Написание текста запроса с раздельными левыми соединениями

В тексте запроса мы обращаемся к табличной части дополнительных реквизитов дважды, задавая уникальные псевдонимы (например, ДопРеквизитыСтоимость и ДопРеквизитыАмортизация):


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

    // Первое соединение — для получения первоначальной стоимости
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ОсновныеСредства.ДополнительныеРеквизиты КАК ДопРеквизитыСтоимость
        ПО ПервоначальныеСведения.ОсновноеСредство = ДопРеквизитыСтоимость.Ссылка
        И ДопРеквизитыСтоимость.Свойство = &СвойствоСтоимость

    // Второе соединение — для получения накопленной амортизации
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ОсновныеСредства.ДополнительныеРеквизиты КАК ДопРеквизитыАмортизация
        ПО ПервоначальныеСведения.ОсновноеСредство = ДопРеквизитыАмортизация.Ссылка
        И ДопРеквизитыАмортизация.Свойство = &СвойствоАмортизация

Этот метод отлично подходит, если вам нужно вытащить 2-3 дополнительных реквизита. Код остается читаемым, а структура запроса — простой.


Решение №2: Одно соединение с группировкой и условием выборки (Быстрый способ)

Что делать, если реквизитов не 2, а 10? Использование десяти левых соединений существенно замедлит выполнение запроса, так как СУБД придется выполнять многократное связывание таблиц.

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

Рассмотрим, как реализовать этот алгоритм:

Шаг 1. Передача списка свойств в параметры

Мы можем объединить наши свойства в массив и передать его в один параметр:


МассивСвойств = Новый Массив;
МассивСвойств.Добавить(ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Первоначальная стоимость (ДМИ)"));
МассивСвойств.Добавить(ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Накопленная амортизация (ДМИ)"));

Запрос.УстановитьПараметр("МассивСвойств", МассивСвойств);
// Также передадим свойства по отдельности для конструкции ВЫБОР внутри запроса
Запрос.УстановитьПараметр("СвойствоСтоимость", МассивСвойств[0]);
Запрос.УстановитьПараметр("СвойствоАмортизация", МассивСвойств[1]);

Шаг 2. Построение запроса с группировкой

В запросе мы соединяем таблицу дополнительных реквизитов всего один раз, накладывая фильтр по массиву свойств. Затем мы группируем результат по основному средству, а значения реквизитов распределяем по колонкам:


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

    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ОсновныеСредства.ДополнительныеРеквизиты КАК ДопРеквизиты
        ПО ПервоначальныеСведения.ОсновноеСредство = ДопРеквизиты.Ссылка
        И ДопРеквизиты.Свойство В (&МассивСвойств)

СГРУППИРОВАТЬ ПО
    ПервоначальныеСведения.ОсновноеСредство,
    ПервоначальныеСведения.ИнвентарныйНомер

Этот подход работает гораздо быстрее на крупных базах данных, поскольку СУБД сканирует таблицу дополнительных реквизитов только один раз.


Решение №3: Программное получение свойств средствами БСП (Без запросов)

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

Вариант А. Получение одного конкретного свойства

Для получения значения конкретного свойства у объекта воспользуемся функцией общего модуля УправлениеСвойствами:


СсылкаНаОС = ВыборкаОбъектов.Ссылка;
СвойствоСтоимость = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Первоначальная стоимость (ДМИ)");

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

Вариант Б. Получение всех свойств объекта сразу

Если нужно прочитать все заполненные свойства для объекта, можно получить их в виде таблицы значений или структуры:


СсылкаНаОС = ВыборкаОбъектов.Ссылка;

// Функция возвращает таблицу со всеми свойствами и значениями для объекта
ТаблицаСвойств = УправлениеСвойствами.ЗначенияСвойств(СсылкаНаОС);

Для Каждого СтрокаСвойства Из ТаблицаСвойств Цикл
    Сообщить("Свойство: " + СтрокаСвойства.Свойство + " | Значение: " + СтрокаСвойства.Значение);
КонецЦикла;

Использование механизмов БСП делает ваш код более лаконичным, снижает вероятность ошибок соединения таблиц и гарантирует совместимость при обновлениях конфигурации.


Полезные советы по хорошему тону разработки в 1С

  1. Откажитесь от метода НайтиПоНаименованию(). Поиск элементов справочников или планов видов характеристик по наименованию в коде — это частая причина будущих сбоев. Пользователь в режиме «1С:Предприятие» может в любой момент исправить опечатку в названии реквизита или переименовать его. Код сразу перестанет работать. Вместо этого используйте поиск по уникальному имени (идентификатору разработчика), если конфигурация это поддерживает, или выносите выбор свойств в настройки отчета/обработки.
  2. Всегда проверяйте связи в Конструкторе запросов. Ошибки вроде случайного кросс-соединения таблиц (через запятую) чаще всего происходят при ручном редактировании текста запроса. Старайтесь всегда открывать запрос в встроенном Конструкторе. Он наглядно визуализирует связи таблиц и предупредит о наличии некорректных или висящих соединений.
  3. Контролируйте типы данных. Значения дополнительных реквизитов в табличной части хранятся в поле с типом Характеристика.ДополнительныеРеквизитыИСведения (обычно это составной тип данных). При выводе в отчеты или при расчетах может потребоваться явное приведение типов с помощью функции ВЫРАЗИТЬ(... КАК ...) в запросе.
← На главную