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