Среди разработчиков 1С конструкция «запрос внутри цикла» считается классическим антипаттерном. Однако у начинающих специалистов нередко возникает вопрос: если объем данных мал (например, до 100 итераций), а алгоритм выполняется за доли секунды, допустимо ли оставить такой код ради простоты разработки? Проанализируем технические нюансы работы платформы 1С и СУБД, чтобы понять, почему такое решение несет в себе скрытые риски и как правильно переписать алгоритм.
Запрет на использование запросов в цикле обусловлен не просто формальным правилом, а фундаментальной архитектурой клиент-серверного взаимодействия платформы 1С:Предприятие:
rphost) к серверу СУБД по сети. Даже если сам запрос выполняется за 0.001 секунды, суммарное время сетевых задержек на передачу команд и получение ответа при 100 итерациях может составить ощутимую паузу.Запрос, РезультатЗапроса и ВыборкаИзРезультатаЗапроса создается лишняя нагрузка на сборщик мусора и менеджер памяти рабочего процесса платформы.Рассмотрим базовую задачу: необходимо получить реквизиты для списка объектов. Вместо выполнения одиночных запросов для каждого элемента по очереди, соберем массив ссылок и передадим его в один запрос целиком.
Посмотрим на пример правильного получения данных:
МассивСсылок = Новый Массив;
Для Каждого Стр Из ТаблицаИсходная Цикл
МассивСсылок.Добавить(Стр.Номенклатура);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура,
| Номенклатура.Артикул КАК Артикул,
| Номенклатура.СтавкаНДС КАК СтавкаНДС
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&МассивСсылок)";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
ТаблицаРезультат.Индексы.Добавить("Номенклатура");
Для Каждого Стр Из ТаблицаИсходная Цикл
НайденнаяСтрока = ТаблицаРезультат.Найти(Стр.Номенклатура, "Номенклатура");
Если НайденнаяСтрока <> Неопределено Тогда
Стр.Артикул = НайденнаяСтрока.Артикул;
КонецЕсли;
КонецЦикла;
Разберем ситуацию, когда поиск должен производиться не по одному полю, а по комбинации параметров (например, номенклатура и характеристика). В этом случае массив передать недостаточно. Оптимальным решением будет выгрузка параметров во временную таблицу с последующим соединением с таблицами базы данных.
Посмотрим, как устроен этот алгоритм:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходныеДанные.Номенклатура КАК Номенклатура,
| ИсходныеДанные.Характеристика КАК Характеристика
|ПОМЕСТИТЬ ВТ_Критерии
|ИЗ
| &ТаблицаКритериев КАК ИсходныеДанные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Критерии.Номенклатура КАК Номенклатура,
| ВТ_Критерии.Характеристика КАК Характеристика,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
|ИЗ
| ВТ_Критерии КАК ВТ_Критерии
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, ) КАК ЦеныНоменклатурыСрезПоследних
| ПО ВТ_Критерии.Номенклатура = ЦеныНоменклатурыСрезПоследних.Номенклатура
| И ВТ_Критерии.Характеристика = ЦеныНоменклатурыСрезПоследних.Характеристика";
Запрос.УстановитьПараметр("ТаблицаКритериев", ТаблицаИсходныхДанных);
РезультатЗапроса = Запрос.Выполнить();
Если после выборки требуется оперативный доступ к данным по ключу, удобно использовать объект Соответствие. Это обеспечивает мгновенный поиск со сложностью O(1) без повторных обращений к базе данных.
Разберем по шагам реализацию данного подхода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура В (&МассивНоменклатуры)) КАК Цены";
Запрос.УстановитьПараметр("Период", ТекущаяДатаСеанса());
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
КэшЦен = Новый Соответствие;
Пока Выборка.Следующий() Цикл
КэшЦен.Вставить(Выборка.Номенклатура, Выборка.Цена);
КонецЦикла;
// Быстрое получение значений в цикле без запросов к СУБД
Для Каждого Стр Из ТаблицаИсходная Цикл
Цена = КэшЦен[Стр.Номенклатура];
Если Цена <> Неопределено Тогда
Стр.Цена = Цена;
КонецЕсли;
КонецЦикла;
Существуют редкие специализированные сценарии, когда платформенные ограничения или требования к надежности делают вызов запроса в цикле допустимым:
Недостаточно памяти) и наложению длительных блокировок. В таких задачах данные выбирают порциями (например, по 1000–5000 штук) через условие ГДЕ Ссылка > &ПоследняяСсылка УПОРЯДОЧИТЬ ПО Ссылка внутри цикла порционной обработки.Таким образом, во всех типовых прикладных задачах (заполнение документов, формирование отчетов, обработка табличных частей) запросов в цикле следует избегать, заменяя их пакетными запросами, временными таблицами или предварительным кэшированием данных в памяти.