При разработке в 1С многие программисты рано или поздно сталкиваются с неожиданным поведением кода: при попытке выгрузить результат выполнения запроса в таблицу значений количество строк многократно увеличивается. Например, вместо ожидаемых 16 документов таблица заполняется 256 строками, где каждая запись повторяется по 16 раз. Давайте подробно разберем эту ситуацию, проанализируем причину ошибки и рассмотрим несколько правильных способов выгрузки результатов запроса.
Посмотрим на пример кода, который приводит к подобной проблеме. Разработчик выполняет запрос, а затем пытается перенести данные в таблицу значений:
РезультатЗапроса = Запрос1.Выполнить();
Таблица = РезультатЗапроса.Выгрузить();
Выборка = РезультатЗапроса.Выбрать();
ТЗ.Очистить();
Ном = 0;
Пока Выборка.Следующий() Цикл
Для каждого стр из Таблица Цикл
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Номер = Ном + 1;
НоваяСтрока.ЗаказКлиента = стр.Ссылка;
// ... заполнение других полей
Ном = Ном + 1;
КонецЦикла;
КонецЦикла;
Проанализируем этот код по шагам. Главная ошибка кроется в использовании сразу двух вложенных обходов: внешнего цикла Пока Выборка.Следующий() Цикл и внутреннего цикла Для каждого стр из Таблица Цикл.
Что происходит на самом деле? Внешний цикл проходит по всем 16 записям результата запроса. Внутренний цикл также содержит те же самые 16 записей, выгруженных в переменную Таблица. Таким образом, для каждой из 16 строк выборки система 16 раз обходит всю таблицу целиком, добавляя новую строку в ТЗ. В результате получается декартово произведение: 16 * 16 = 256 строк. Чтобы исправить эту ошибку, нам необходимо отказаться от одного из циклов.
Если наша задача состоит лишь в том, чтобы перенести данные из результата запроса в таблицу значений (которая уже имеет нужные колонки), мы можем вообще избавиться от циклов. Для этого рассмотрим метод Загрузить().
Чтобы этот способ сработал идеально, нам нужно переименовать поля в самом тексте запроса с помощью ключевого слова КАК так, чтобы они в точности совпадали с именами колонок нашей таблицы значений.
// Выполняем запрос и сразу выгружаем его в новую таблицу
ТаблицаРезультат = Запрос1.Выполнить().Выгрузить();
// Загружаем данные в нашу исходную таблицу значений
ТЗ.Загрузить(ТаблицаРезультат);
Важно: метод Загрузить() полностью очищает старые данные в таблице-приемнике и загружает новые. Колонки сопоставляются по именам.
Если таблица-приемник содержит дополнительные колонки, которые нельзя стирать, или если мы не можем полностью синхронизировать имена полей запроса и таблицы, метод Загрузить() нам не подойдет. В этом случае мы будем использовать правильный одинарный цикл выборки и встроенную функцию ЗаполнитьЗначенияСвойств().
Рассмотрим, как это работает на практике:
Выборка = Запрос1.Выполнить().Выбрать();
ТЗ.Очистить();
Ном = 0;
Пока Выборка.Следующий() Цикл
НоваяСтрока = ТЗ.Добавить();
Ном = Ном + 1;
НоваяСтрока.Номер = Ном;
// Автоматически заполняем колонки с совпадающими именами
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;
Данный подход делает код очень аккуратным. Нам не нужно вручную перечислять десятки колонок для присвоения. Платформа сама найдет свойства (поля) в Выборка, имена которых совпадают с колонками в НоваяСтрока, и перенесет значения.
При работе с запросами у нас всегда есть выбор: получить результат в виде таблицы значений через Выгрузить() или использовать курсор базы данных через Выбрать(). Разберем разницу между ними.
Выгрузить() забирает из базы данных сразу весь объем полученных строк и помещает его в оперативную память сервера в виде объекта таблицы значений. Если ваш запрос вернет 500 000 записей, это может вызвать острую нехватку оперативной памяти и зависание сервера.Выбрать() создает так называемый курсор. Он не тянет все данные в память разом, а считывает их небольшими порциями (кэшами) по мере того, как мы вызываем метод Следующий() в цикле. Это гораздо более ресурсосберегающий способ при работе с большими объемами данных.Также стоит отметить особенность метода Выгрузить() при использовании итогов. Если в вашем запросе есть секция ИТОГИ, вы можете выгрузить результат не в плоскую таблицу, а в дерево значений. Для этого нужно передать соответствующий параметр:
Дерево = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
Если таблица значений ТЗ, которую мы хотим заполнить, является реквизитом управляемой формы, мы не сможем просто присвоить ей результат через знак равенства или использовать метод Загрузить() на клиенте. Объекты базы данных и методы запросов недоступны в клиентском контексте (на стороне веб-браузера или тонкого клиента).
Посмотрим по шагам, как правильно вывести результат на форму:
&НаСервере).ЗначениеВРеквизитФормы() для передачи данных в реквизит формы.
&НаСервере
Процедура ЗаполнитьТаблицуНаСервере()
Запрос = Новый Запрос;
Запрос.Текст = "..."; // Текст запроса
// Получаем локальную таблицу значений
ТаблицаРез = Запрос.Выполнить().Выгрузить();
// Переносим данные в реквизит управляемой формы с именем "ТЗ"
ЗначениеВРеквизитФормы(ТаблицаРез, "ТЗ");
КонецПроцедуры
Подводя итоги, мы проанализировали частую ошибку двойного перебора, из-за которой множатся строки. Чтобы избежать подобного, всегда используйте только один обход данных, применяйте метод Загрузить() для быстрых переносов или ЗаполнитьЗначенияСвойств() при построчном обходе выборки.