Как вернуться на начало первого цикла из второго во вложенных циклах 1С

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

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

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

В этой статье мы подробно рассмотрим четыре практических способа решения этой задачи: от самых простых (с использованием флажков) до наиболее профессиональных с точки зрения архитектуры и производительности 1С.

Вариант 1. Использование переменной-флага (Быстрый обходной путь)

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

Рассмотрим пример реализации этого подхода:


Для Каждого Стр Из МассивСтрок Цикл
    
    // Сбрасываем флаг прерывания для каждой новой итерации внешнего цикла
    ТребуетсяПропустить = Ложь;
    
    // Какой-то предварительный код обработки строки
    ДанныеСтроки = Стр.Значение;
    
    Для Каждого Запись Из НаборЗаписей Цикл
        
        Если Запись.Ключ = ДанныеСтроки Тогда
            // Условие выполнено, нам нужно завершить обработку текущей "Стр"
            ТребуетсяПропустить = Истина;
            Прервать; // Выходим из внутреннего цикла
        КонецЕсли;
        
    КонецЦикла;
    
    // Проверяем флаг сразу после завершения внутреннего цикла
    Если ТребуетсяПропустить Тогда
        Продолжить; // Переходим к следующей строке "Стр" в внешнем цикле
    КонецЕсли;
    
    // Код ниже НЕ выполнится, если сработал флаг ТребуетсяПропустить
    ВыполнитьЗаписьВБазуДанных(Стр);
    
КонецЦикла;

Преимущества метода:

  1. Простота понимания и быстрая скорость написания «на коленке».
  2. Не требует изменения структуры методов модуля.

Недостатки метода:

Вариант 2. Вынесение вложенного цикла в отдельную функцию (Рекомендуемый стандарт)

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

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

Посмотрим на пример практической реализации:


Для Каждого Стр Из МассивСтрок Цикл
    
    // Вызываем функцию проверки. Если она возвращает Ложь, 
    // мы пропускаем выполнение оставшегося кода для текущей строки.
    Если Не ПроверитьНеобходимостьОбработки(Стр, НаборЗаписей) Тогда
        Продолжить;
    КонецЕсли;
    
    // Этот код выполняется только тогда, когда функция вернула Истина
    ВыполнитьЗаписьВБазуДанных(Стр);
    
КонецЦикла;

// Вспомогательная функция, инкапсулирующая вложенный цикл
Функция ПроверитьНеобходимостьОбработки(ТекущаяСтрока, НаборЗаписей)
    
    ДанныеСтроки = ТекущаяСтрока.Значение;
    
    Для Каждого Запись Из НаборЗаписей Цикл
        Если Запись.Ключ = ДанныеСтроки Тогда
            // Условие не выполнено, сразу возвращаем Ложь и выходим из метода
            Возврат Ложь;
        КонецЕсли;
    КонецЦикла;
    
    // Если обошли весь цикл и условие не сработало — возвращаем Истина
    Возврат Истина;
    
КонецФункции

Преимущества метода:

  1. Чистота кода: Отсутствуют лишние переменные-флаги во внешнем методе.
  2. Читаемость: Код внешнего цикла становится лаконичным и понятным с первого взгляда.
  3. Повторное использование: Функцию ПроверитьНеобходимостьОбработки можно использовать в других частях программы.
  4. Легкость отладки: Можно поставить точку останова на операторе Возврат и быстро понять логику работы программы.

Вариант 3. Использование оператора перехода Перейти (Goto) — почему это плохо

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

Синтаксис выглядит следующим образом:


Для Каждого Стр Из МассивСтрок Цикл
    
    Для Каждого Запись Из НаборЗаписей Цикл
        Если Запись.Ключ = Стр.Значение Тогда
            Перейти ~КонецВнешнейИтерации;
        КонецЕсли;
    КонецЦикла;
    
    ВыполнитьЗаписьВБазуДанных(Стр);
    
    ~КонецВнешнейИтерации:
    // Сюда передается управление при срабатывании условия перехода
КонецЦикла;

Важное предупреждение: Несмотря на то, что этот код работоспособен, использование оператора Перейти крайне не рекомендуется официальными стандартами разработки фирмы «1С». Использование меток делает код запутанным, усложняет чтение программы другими разработчиками и часто приводит к логическим ошибкам при последующем рефакторинге. Автоматические системы проверки качества кода (например, АПК) всегда помечают оператор Перейти как критическую ошибку.

Вариант 4. Оптимизация алгоритма через запросы (Архитектурно верное решение)

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

Выполнение запросов или обращений к базе данных (создание наборов записей) внутри циклов — это **критическая ошибка производительности** в 1С. При обработке файлов объемом более 1000 строк система начнет сильно зависать из-за постоянных обращений к СУБД в цикле.

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

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

Рассмотрим пример реализации такого алгоритма:


// Шаг 1. Создаем таблицу значений для загружаемых данных
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Ключ", Новый ОписаниеТипов("Строка"));
ТаблицаДанных.Колонки.Добавить("Значение", Новый ОписаниеТипов("Число"));

// Заполняем таблицу данными (например, из Excel)
Для Каждого СтрокаФайла Из ДанныеФайла Цикл
    НоваяСтрока = ТаблицаДанных.Добавить();
    НоваяСтрока.Ключ = СтрокаФайла.Ячейка1;
    НоваяСтрока.Значение = СтрокаФайла.Ячейка2;
КонецЦикла;

// Шаг 2. Создаем запрос с использованием временных таблиц
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Передаем таблицу значений в параметр запроса
Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);

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

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

// Шаг 3. Обходим плоский результат без вложенных циклов
Пока Выборка.Следующий() Цикл
    
    Если Выборка.ЭтоНоваяЗапись Или Выборка.ТребуетсяОбновить Тогда
        // Выполняем запись изменений в базу данных
        ЗаписатьДанныеВРегистр(Выборка.Ключ, Выборка.ЗначениеНовое);
    КонецЕсли;
    
    // Нам больше не нужны вложенные циклы, флаги или Goto!
    // Мы сразу получаем готовую аналитику по каждой строке.
    
КонецЦикла;

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

  1. Колоссальное ускорение: Время обработки больших объемов данных сокращается в десятки и сотни раз.
  2. Отсутствие блокировок: База данных не блокируется постоянными мелкими транзакциями чтения-записи.
  3. Чистота логики: Вся сложная условная логика переносится на уровень СУБД, снижая вероятность ошибок в программном коде 1С.

Резюме

Для простых алгоритмов, не связанных с частыми обращениями к базе данных, идеальным решением будет Вариант 2 (вынесение логики в отдельную функцию). Это сделает ваш код чистым, понятным и соответствующим стандартам 1С.

Если же вы выполняете сопоставление больших объемов данных с объектами информационной базы, настоятельно рекомендуем использовать Вариант 4 (запросы и временные таблицы). Это позволит избежать падения производительности и обеспечит стабильную работу системы.

← На главную