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

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

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

Анализ типичных ошибок в исходном коде

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

  1. Ошибка «Поле объекта не обнаружено» при установке отбора: Объект РегистрСведенийНаборЗаписей содержит в свойстве Отбор только те поля, которые являются измерениями регистра (а также Период и Регистратор при соответствующих настройках). Если имя измерения указано с опечаткой или поле является ресурсом/реквизитом, обращение к СписокЦенЗакупки.Отбор.Номенклатура вызывает фатальную ошибку.
  2. Нецелевое использование набора записей: Объект РегистрСведенийНаборЗаписей предназначен для физической записи и чтения порций данных из базы. Для периодического регистра сведений чтение набора возвращает всю историю изменений без автоматического среза на дату документа.
  3. Поиск ссылок по наименованию внутри цикла: Вызов Справочники.Номенклатура.НайтиПоНаименованию() является избыточным, так как реквизит табличной части Строка.Номенклатура уже содержит готовую ссылку типа СправочникСсылка.Номенклатура. Дополнительный поиск в цикле создает лишние неявные запросы к СУБД.
  4. Несоответствие типов данных: Присвоение Строка.ЦенаЗакупочные = СтрокаРегистраСведений; приводит к ошибке, так как переменная строки регистра является объектом строки набора, а реквизит табличной части ожидает числовое значение из конкретного ресурса (например, СтрокаРегистраСведений.Цена).

Способ 1. Получение среза цен одним запросом и обход выборки

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


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

Способ 2. Использование объекта Соответствие (хеш-таблицы)

Если табличная часть содержит тысячи строк, поиск через Выборка.НайтиСледующий() может выполнять линейный перебор. Для максимального быстродействия перенесем результат запроса в объект Соответствие, где поиск по ключу выполняется за константное время O(1):


Процедура ЗаполнитьЦеныЧерезСоответствие(ДокументОбъект, ТипЦен)
    
    МассивНоменклатуры = ДокументОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Цены.Номенклатура КАК Номенклатура,
        |	Цены.Цена КАК Цена
        |ИЗ
        |	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, 
        |		Номенклатура В (&МассивНоменклатуры) И ТипЦен = &ТипЦен) КАК Цены";
        
    Запрос.УстановитьПараметр("Период", ДокументОбъект.Дата);
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    Запрос.УстановитьПараметр("ТипЦен", ТипЦен);
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    // Заполняем хеш-таблицу соответствия "Номенклатура -> Цена"
    ЦеныНоменклатуры = Новый Соответствие;
    Пока Выборка.Следующий() Цикл
        ЦеныНоменклатуры.Вставить(Выборка.Номенклатура, Выборка.Цена);
    КонецЦикла;
    
    // Быстрый обход строк документа
    Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл
        НайденнаяЦена = ЦеныНоменклатуры[СтрокаТЧ.Номенклатура];
        СтрокаТЧ.Цена = ?(НайденнаяЦена <> Неопределено, НайденнаяЦена, 0);
    КонецЦикла;
    
КонецПроцедуры

Способ 3. Пакетный запрос с временной таблицей

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

  1. Выгружаем табличную часть документа в объект ТаблицаЗначений.
  2. Помещаем таблицу во временную таблицу через МенеджерВременныхТаблиц и создаем индекс по полю Номенклатура.
  3. Выполняем левое соединение временной таблицы с виртуальной таблицей РегистрСведений.ЦеныНоменклатуры.СрезПоследних.
  4. Выгружаем готовый результат запроса обратно в табличную часть документа методом ДокументОбъект.Товары.Загрузить().

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

Способ 4. Получение цены без написания текста запроса (метод ПолучитьПоследнее)

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


ОтборЦен = Новый Структура;
ОтборЦен.Вставить("Номенклатура", СсылкаНаНоменклатуру);
ОтборЦен.Вставить("ТипЦен", СсылкаНаТипЦен);

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

Если ЗначенияРесурсов.Свойство("Цена") Тогда
    ТекущаяЦена = ЗначенияРесурсов.Цена;
КонецЕсли;

Важно: Помните, что вызов метода ПолучитьПоследнее() внутри цикла по строкам табличной части крайне не рекомендуется. Платформа 1С под капотом все равно формирует отдельный SQL-запрос на каждый вызов метода, что приведет к существенным задержкам при проведении и обработке больших документов.

← На главную