При разработке отчетов и обработок в среде 1С:Предприятие 8 программисты часто сталкиваются с необходимостью выгрузки данных во внешние приложения, такие как Microsoft Excel. Одной из типичных проблем, возникающих при написании сложных циклов обработки данных, является ситуация, когда выборка запроса ведет себя «странно»: в одном случае данные перебираются корректно, а в другом — код бесконечно (или многократно) обращается к одной и той же записи. Рассмотрим подробнее, почему это происходит, и разберем фундаментальные механизмы работы с объектами Запрос и ВыборкаИзРезультатаЗапроса.
Проанализируем сценарий, описанный автором темы. В первом варианте кода (который работает) используется вложенный цикл. Основная выборка перебирает сотрудников, и внутри этого перебора данные сразу записываются в ячейки Excel. Во втором варианте автор попытался оптимизировать или разделить логику, вынеся обработку разных периодов в отдельные блоки с повторным выполнением запроса.
Основная ошибка заключалась в том, что внутри циклов Пока Выборка.Следующий() данные считывались не из текущей (свежей) выборки Выборка, а из старой переменной ВыборкаДетальныеЗаписи, которая осталась от предыдущего выполнения кода. Выясним причину, почему это приводит к «зацикливанию» на одном сотруднике.
Когда мы вызываем метод РезультатЗапроса.Выбрать(), система создает объект ВыборкаИзРезультатаЗапроса. Этот объект содержит внутри себя «курсор» (указатель), который изначально стоит перед первой записью. Метод Следующий() сдвигает этот курсор на одну позицию вперед.
Рассмотрим, что произошло в неработающем коде автора:
ВыборкаДетальныеЗаписи.ВыборкаДетальныеЗаписи остановился на последней записи.Выборка и начинает цикл по ней.Лист.Cells(СТР, 1).Value = ВыборкаДетальныеЗаписи.Сотрудник.Поскольку ВыборкаДетальныеЗаписи больше не перемещается (ее метод Следующий() в этом блоке не вызывается), она всегда возвращает данные той самой последней записи, на которой остановилась ранее. Программа честно выполняет цикл столько раз, сколько строк в новой выборке, но значения каждый раз берет из старой. Именно поэтому в таблицу Excel записывался один и тот же сотрудник много раз.
В коде автора встречается строка: Запрос3 = Запрос;. Это критически важный момент для понимания архитектуры 1С. В языке 1С сложные объекты (запросы, массивы, таблицы значений) передаются и присваиваются по ссылке, а не по значению.
Проанализируем ситуацию: когда вы пишете Запрос3 = Запрос, вы не создаете новый независимый объект. Вы просто создаете вторую «переменную-указатель», которая смотрит на тот же самый экземпляр запроса в памяти. Если вы измените Запрос3.Текст, то текст изменится и у оригинального объекта Запрос. Это может привести к непредсказуемым результатам в дальнейшей работе программы, если вы рассчитываете, что в исходной переменной сохранился старый текст запроса.
Правильный подход для создания копии (если это действительно необходимо) — инициализация нового объекта:
Запрос3 = Новый Запрос;
Запрос3.Текст = Запрос.Текст; // Копируем текст
// Копируем параметры, если нужно
Для Каждого Параметр Из Запрос.Параметры Цикл
Запрос3.УстановитьПараметр(Параметр.Ключ, Параметр.Значение);
КонецЦикла;
Чтобы код заработал, необходимо синхронизировать переменную, по которой идет цикл, и переменную, из которой считываются данные. Посмотрим на исправленный фрагмент кода:
// Выполняем измененный запрос
Рез = Запрос3.Выполнить();
Выборка = Рез.Выбрать();
Пока Выборка.Следующий() Цикл
// Используем данные из ТЕКУЩЕЙ выборки, а не из старой!
ЛистРазрез3.Cells(СТР, 1).Value = Строка(Выборка.Сотрудник);
ЛистРазрез3.Cells(СТР, 2).Value = Число(Выборка[МесяцМинус3]);
СТР = СТР + 1;
КонецЦикла;
Здесь мы видим, что данные извлекаются из объекта Выборка, который в данный момент итерируется методом Следующий(). Это гарантирует получение актуальных данных на каждом шаге цикла.
Хотя исправление имен переменных решает проблему «одного сотрудника», сам метод работы через Excel.Application (COM-объект) внутри циклов запроса является крайне медленным. Рассмотрим, как можно улучшить производительность.
Каждое обращение к Cells(СТР, Колонка).Value инициирует межпроцессное взаимодействие между 1С и Excel, что занимает значительное время. Если у вас 1000 сотрудников и 12 месяцев, это тысячи вызовов. Проанализируем более эффективные методы:
ТаблицаЗначений методом РезультатЗапроса.Выгрузить(). С таблицей в памяти 1С работает в десятки раз быстрее, чем с выборкой.Value диапазона Range.ТабличныйДокумент.Записать().Рассмотрим пример, который объединяет правильную логику работы с данными и подготовку к эффективной выгрузке:
Запрос = Новый Запрос(ТекстЗапроса);
// Установка параметров...
Результат = Запрос.Выполнить();
// Вместо мучительного перебора выборки выгружаем всё в ТЗ
ТаблицаДанных = Результат.Выгрузить();
// Теперь мы можем работать с данными ТЗ, искать в ней строки,
// сортировать или группировать без повторных запросов к БД
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
// Пример поиска соответствия в Excel (хотя лучше заполнять Excel из ТЗ)
// РаздельноФИО = СтрРазделить(СтрокаДанных.Сотрудник, " ");
// ... логика заполнения ...
КонецЦикла;
Подводя итог, выделим три правила, которые помогут избежать подобных ошибок в будущем:
Во-первых, всегда проверяйте соответствие переменной в условии цикла Пока ... Следующий() и переменных, используемых внутри тела цикла. Ошибки копирования (Copy-Paste) — самый частый источник «замерших» данных.
Во-вторых, помните о ссылочной природе объектов 1С. Изменение «копии» запроса, созданной через простое равенство, меняет оригинал.
В-третьих, используйте инструменты отладки. Если вы видите, что данные не меняются, поставьте точку останова внутри цикла и посмотрите в «Табло», какое значение имеет переменная выборки и на какой строке она находится (свойство НомерСтроки).
Соблюдение этих принципов сделает ваш код более надежным, а процесс разработки — предсказуемым.