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