К статье
Практический материал

Разработка и Конфигурация · 13 мин

1С: как избежать запроса в цикле

Как убрать запрос из цикла в 1С: пакетные запросы, временные таблицы, соединения, выборка по массиву. Примеры кода, типичные ошибки и способы проверки результата.

Запрос в цикле — самая частая причина медленной работы обработок, отчётов и проведения документов в 1С. Каждый вызов Запрос.Выполнить() — это обращение к СУБД, сетевой обмен, разбор текста запроса и создание служебных объектов. Если цикл выполняет тысячу итераций, вы получаете тысячу обращений к серверу баз данных, и операция, которая могла бы занять секунды, растягивается на минуты.

Хорошая новость: в большинстве случаев запрос в цикле — признак не «тяжёлой задачи», а неудачной архитектуры кода. Платформа 1С предоставляет достаточно инструментов, чтобы получить все нужные данные одним-двумя запросами и обрабатывать их в памяти: параметры-массивы, временные таблицы, пакетные запросы, соединения и группировки.

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

Как распознать проблему и оценить её масштаб

Типичный антипаттерн выглядит так: обходится табличная часть документа или список контрагентов, и на каждой итерации выполняется запрос — получить остаток по товару, цену, задолженность, реквизит договора. Внешне код работает правильно, но время выполнения растёт линейно от количества строк.

Признаки проблемы заметны и без замеров: документ с сотней строк проводится десятки секунд, отчёт по большому периоду «подвисает», пользователи жалуются на паузы при массовых операциях. Дополнительный индикатор — в технологическом журнале или консоли запросов видно множество однотипных запросов, отличающихся только значением параметра.

Чтобы оценить масштаб, посчитайте, сколько раз выполняется запрос: это произведение количества итераций цикла на количество запросов внутри него. Обработка 5000 строк с двумя запросами внутри — это 10 000 обращений к СУБД. Даже при быстром запросе в 5–10 мс накладные расходы на выполнение из кода 1С дадут заметную задержку.

Запрос внутри циклаN обращений к СУБД

Переписать на один запрос с условием по массиву или временной таблице

Запрос в условии циклаСкрытое обращение на каждой итерации

Вынести результат в переменную до цикла

Методы ПолучитьОбъект/НайтиПоКоду в циклеНеявные запросы к базе

Заменить выборкой ссылок одним запросом

Время растёт пропорционально строкамЛинейная зависимость от данных

Проверить, есть ли обращения к БД внутри обхода

Базовый приём: условие по массиву вместо запроса на каждую строку

Самый простой и самый частый случай: внутри цикла по табличной части нужно получить данные по каждой номенклатуре — цену, остаток, характеристику. Вместо запроса на каждую строку соберите все ссылки в массив и выполните один запрос с условием В (&МассивНоменклатуры).

МассивНоменклатуры = Новый Массив;

Для Каждого СтрокаТЧ Из Документ.Товары Цикл

МассивНоменклатуры.Добавить(СтрокаТЧ.Номенклатура);

КонецЦикла;

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Остатки.Номенклатура КАК Номенклатура,

| Остатки.КоличествоОстаток КАК Остаток

|ИЗ

| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (&МассивНоменклатуры)) КАК Остатки";

Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);

Остатки = Запрос.Выполнить().Выгрузить();

Остатки.Индексы.Добавить("Номенклатура");

Затем в цикле используйте поиск по таблице значений: Остатки.Найти(СтрокаТЧ.Номенклатура, "Номенклатура"). Чтобы поиск был быстрым, добавьте индекс на колонку — без индекса метод Найти выполняет полный перебор, и вы просто перенесёте тормоза из СУБД в память.

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

Временные таблицы и пакетные запросы

Когда условие сложнее, чем «ссылка в массиве» — например, нужна пара «номенклатура + характеристика + склад» или данные из другого запроса, — используйте временные таблицы. Источник данных помещается во временную таблицу, а основной запрос соединяется с ней.

Запрос = Новый Запрос;

Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

Запрос.Текст =

"ВЫБРАТЬ

| Товары.Номенклатура КАК Номенклатура,

| Товары.Склад КАК Склад

|ПОМЕСТИТЬ ВТ_Товары

|ИЗ

| &Товары КАК Товары

|;

|////////////////////////////////////////////////////////

|ВЫБРАТЬ

| ВТ.Номенклатура КАК Номенклатура,

| ВТ.Склад КАК Склад,

| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток

|ИЗ

| ВТ_Товары КАК ВТ

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки

| ПО ВТ.Номенклатура = Остатки.Номенклатура

| И ВТ.Склад = Остатки.Склад";

Запрос.УстановитьПараметр("Товары", ТаблицаТоваров);

Параметром временной таблицы может быть таблица значений или результат другого запроса. Это ключевой приём для сложных обработок: промежуточные данные живут на сервере СУБД, и дальнейшие запросы работают с ними без передачи данных в код.

Пакетный запрос (несколько операторов, разделённых точкой с запятой) позволяет выполнить всю цепочку за один вызов ВыполнитьПакет() или получить последний результат через Выполнить(). Это сокращает и количество обращений к СУБД, и объём кода.

Типовые сценарии переписывания

Обращение к реквизитам через точку в цикле

Конструкция СтрокаТЧ.Номенклатура.ЕдиницаИзмерения внутри цикла — тоже скрытый запрос: платформа дочитывает объект из базы при обращении к реквизиту ссылки. При сотнях строк это сотни чтений. Решение — включить нужные реквизиты в основной запрос через точку (Товары.Номенклатура.ЕдиницаИзмерения КАК Единица) — в тексте запроса это бесплатно, СУБД выполнит соединение один раз.

Поиск и создание элементов справочника

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

Расчёт итогов по группам

Если внутри цикла по контрагентам считается сумма задолженности или оборот, замените это группировкой в запросе: СУММА(Обороты.СуммаОборот) с СГРУППИРОВАТЬ ПО Контрагент. СУБД выполнит агрегацию одним проходом по таблице, что на порядки быстрее покадрового расчёта из кода.

Проведение документа с контролем остатков

Классическая ошибка в обработке проведения: для каждой строки табличной части запрашивается остаток. Правильный подход — один запрос по всей табличной части с группировкой по номенклатуре и складу, затем обход выборки и формирование движений и сообщений об отказе. Заодно это устраняет проблему двойного учёта одной позиции, встречающейся в документе дважды.

Порядок переписывания кода
  1. 1
    Найдите все обращения к БД внутри цикла: явные запросы, НайтиПоКоду, ПолучитьОбъект, обращения к реквизитам через точку
  2. 2
    Определите, какие данные реально нужны на каждой итерации
  3. 3
    Соберите ключи (ссылки, коды) в массив или таблицу значений до цикла
  4. 4
    Напишите один запрос с условием В, соединением или временной таблицей
  5. 5
    Выгрузите результат и добавьте индексы на колонки поиска
  6. 6
    Замените тело цикла на поиск по выгруженной таблице или соответствию
  7. 7
    Прогоните обработку на тех же данных и сравните результат со старой версией

Когда запрос в цикле допустим

Не каждый запрос в цикле нужно убирать. Если цикл выполняется 3–5 раз (например, обход нескольких организаций или складов, выбранных пользователем), накладные расходы несущественны, а объединение запросов усложнит код без выигрыша.

Допустимы и случаи, когда на каждой итерации меняется сама логика запроса или параметры зависят от результата предыдущей итерации — например, рекурсивный обход иерархии с ограничением глубины. Даже здесь стоит рассмотреть иерархический запрос (ИЕРАРХИЯ в условии или итоги по иерархии), который часто заменяет рекурсию целиком.

Критерий простой: если количество итераций заранее не ограничено и растёт вместе с данными (строки документов, элементы справочника, записи регистра), запрос нужно выносить из цикла. Если итераций заведомо мало и фиксировано — оставьте код простым.

Маршрут решения

Как выбрать действие

В цикле запрос по одной ссылке или кодуСоберите значения в массив и используйте условие В (&Массив)
Условие по нескольким полям одновременноПередайте таблицу значений во временную таблицу и соединитесь с ней
В цикле считаются суммы или количестваПеренесите расчёт в СУММА() с группировкой в запросе
В цикле обращение к реквизитам ссылки через точкуДобавьте реквизиты в основной запрос через точку
Итераций всегда меньше десяткаОставьте как есть, оптимизация не окупится

Типичные ошибки при оптимизации

Первая ошибка — перенос запроса из цикла без индексации результата. Таблица значений с тысячами строк и поиск методом Найти без индекса — это полный перебор на каждой итерации, итоговая сложность O(N×M). Всегда добавляйте Индексы.Добавить() по колонкам поиска или используйте соответствие.

Вторая ошибка — выборка лишних данных «про запас». Запрос без отбора по массиву, выбирающий все остатки по регистру, может быть медленнее исходного цикла на больших базах. Условие должно ограничивать выборку только нужными строками.

Третья ошибка — изменение логики при переписывании. Например, исходный код при отсутствии остатка пропускал строку, а новый запрос с левым соединением и ЕСТЬNULL возвращает ноль — и поведение обработки меняется. Перед заменой зафиксируйте результат старой версии на тестовом наборе и сравните после.

Типичные ошибки
01
Поиск по таблице значений без индекса — полный перебор вместо обращения к СУБД
02
Запрос без отбора: выбирается весь регистр вместо нужных строк
03
Соединение без ЕСТЬNULL — NULL в результате ломает дальнейшие расчёты
04
Дубли в массиве параметров — лишняя работа СУБД и неочевидные итоги
05
Проверка оптимизации только на маленьких данных — проблема проявится в продакшене

Как проверить результат оптимизации

Замерьте время до и после: оберните участок кода в ТекущаяУниверсальнаяДатаВМиллисекундах() и зафиксируйте разницу на реальном объёме данных, а не на трёх тестовых строках. Эффект от устранения запроса в цикле проявляется именно на больших объёмах.

Сверьте функциональный результат: итоговые суммы, количество обработанных строк, сформированные движения или созданные элементы должны совпадать со старой версией. Удобный способ — выгрузить результаты обеих версий в таблицы значений и сравнить программно.

Дополнительно проверьте граничные случаи: пустая табличная часть (запрос с пустым массивом не должен падать), дублирующиеся позиции, отсутствие данных в регистре. Условие В (&ПустойМассив) корректно возвращает пустой результат, но убедитесь, что последующий код это обрабатывает.

Контрольные точки

Как проверить результат

Время выполнения на реальных данных
Норма

Сократилось в разы и не растёт линейно от объёма

Тревожный признак

Изменений нет или стало медленнее

Итоговые суммы и количество строк
Норма

Полностью совпадают со старой версией

Тревожный признак

Расхождения хотя бы по одной строке

Пустой массив параметров
Норма

Запрос возвращает пустой результат, код не падает

Тревожный признак

Ошибка выполнения или лишние данные

Технологический журнал / консоль
Норма

Один запрос вместо серии однотипных

Тревожный признак

Множество повторяющихся обращений к СУБД

FAQ

Чем плох запрос в цикле, если база небольшая?

На маленькой базе проблема незаметна, но код с запросом в цикле — это отложенная проблема: с ростом данных время выполнения растёт линейно, а нагрузка на СУБД мешает другим пользователям. Дешевле написать правильно сразу, чем переписывать под давлением жалоб.

Что быстрее: соединение с временной таблицей или условие В с массивом?

Для простого отбора по одному полю разница невелика, и условие В с массивом проще в написании. Временная таблица выигрывает, когда условие по нескольким полям, нужны дополнительные данные источника или результат используется в нескольких последующих запросах пакета.

Как убрать обращение к реквизитам ссылки в цикле без запроса?

Если основные данные уже получены не запросом (например, обход табличной части объекта), реквизиты ссылок можно добрать одним дополнительным запросом по массиву ссылок с выборкой нужных полей через точку. Полного способа «дочитать реквизиты пакетно» без запроса в платформе нет.

Влияет ли запрос в цикле на других пользователей?

Да. Каждое выполнение запроса занимает соединение с СУБД, расходует память сервера и может удерживать блокировки при чтении в транзакции. Массовая обработка с запросами в цикле способна заметно замедлить работу всей информационной базы.

Нужно ли выносить запрос из цикла в запросе проведения документа?

Да, это одно из самых критичных мест: проведение выполняется в транзакции, и длительная обработка удерживает блокировки регистров. Один запрос по всей табличной части сокращает время транзакции и снижает вероятность конфликтов блокировок при параллельной работе.

Что делать, если запрос в цикле формируется динамически?

Разделите запрос на постоянную и переменную части. Часто «динамичность» сводится к разным значениям параметров — тогда достаточно одного текста запроса и передачи параметров. Если меняется структура (разные таблицы или поля), рассмотрите несколько готовых шаблонов текста и выбор нужного до цикла.

Итог

Что важно запомнить

Запрос в цикле заменяется одним запросом: массив с условием В, временная таблица с соединением или группировка для итогов. Результат выгружается, индексируется, а цикл работает с данными в памяти. После оптимизации обязательно сравните результат со старой версией на реальном объёме данных.