Как заполнить табличную часть результатом запроса в 1С и избежать очистки данных

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

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

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

Для начала давайте разберем оригинальный код, который вызвал затруднения у автора темы на форуме:


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

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

Ошибка №1: Использование метода Загрузить() внутри цикла обхода выборки

Метод Загрузить() у табличной части объекта (или реквизита формы) предназначен для полной перезаписи всей таблицы. Он работает следующим образом: сначала полностью удаляет все существующие строки в табличной части, а затем загружает новые строки из переданной Таблицы Значений. Помещая этот метод внутрь цикла Пока ВыборкаДетальныеЗаписи.Следующий(), мы заставляем систему на каждой итерации цикла заново очищать всю табличную часть и пытаться загрузить туда всю таблицу результатов запроса. Это не только приводит к катастрофическому падению производительности, но и ломает логику заполнения.

Ошибка №2: Очистка существующих данных в документе

Если в табличной части документа уже выбраны товары, проставлено их количество, цены и суммы, то вызов метода Загрузить() сотрет все эти данные. Если в результате запроса содержатся только две колонки — Номенклатура и МестоХранения, то после выполнения метода Загрузить() у вас в документе останутся только эти две колонки, а все остальные важные реквизиты строк (количество, цена, сумма, ставка НДС) будут стерты и станут пустыми.

Ошибка №3: Неверное понимание контекста и параметра запроса

Автор пытается передать в параметр запроса значение Ссылка: Запрос.УстановитьПараметр("Ссылка", Ссылка). Поскольку данный код выполняется в модуле формы документа, свойство Ссылка возвращает ссылку на сам открытый документ (например, "Инвентаризация товаров №0001"). В то же время в условии запроса написано: МестаХраненияНоменклатуры.Номенклатура.Ссылка = &Ссылка. Происходит сравнение ссылки на элемент справочника Номенклатура со ссылкой на документ Инвентаризация. Разумеется, типы данных не совпадают, условие никогда не выполняется, и запрос всегда возвращает пустой результат. Именно поэтому при нажатии кнопки заполнения табличная часть документа просто полностью очищалась.


Способ №1: Точечное обновление строк через объект «Соответствие» (Рекомендуемый)

Если перед нами стоит задача заполнить только одну колонку (например, МестоХраненияНаСкладе) для тех товаров, которые уже добавлены пользователем в табличную часть документа, крайне важно сохранить все остальные данные в строках. Для этого мы не должны перезаписывать всю табличную часть целиком. Вместо этого мы точечно обновим нужное поле.

Давайте рассмотрим эффективный алгоритм, оптимизированный для работы на управляемых формах 1С 8.3. Мы будем использовать объект Соответствие, чтобы сопоставить номенклатуру с ее местом хранения в оперативной памяти без циклических обращений к базе данных.

Выполним следующие шаги:

  1. Выгрузим список уникальных товаров из табличной части, чтобы отправить в запрос только те позиции, которые реально присутствуют в документе.
  2. Выполним пакетный запрос к регистру сведений с условием поиска В (&МассивНоменклатуры).
  3. Поместим результаты запроса в объект Соответствие (аналог хэш-таблицы, где ключом будет номенклатура, а значением — место хранения).
  4. Быстро обойдем строки табличной части документа и заполним поле места хранения.

Посмотрим на пример реализации этого метода в модуле управляемой формы:


&НаКлиенте
Процедура ЗаполнитьМестаХранения(Команда)
    // Передаем управление на сервер для работы с базой данных
    ЗаполнитьМестаХраненияНаСервере();
КонецПроцедуры

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

Преимущества этого подхода:


Способ №2: Полное перезаполнение через Левое Соединение во Временной Таблице

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

Давайте разберем по шагам, как реализовать этот элегантный архитектурный прием:


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

Важные особенности этого метода:


Подведем итоги

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

← На главную