Почему выборка запроса в 1С работает некорректно и выводит одного и того же сотрудника?

Программист 1С v8.3 (Обычные формы)
← На главную

При разработке отчетов и обработок в среде 1С:Предприятие 8 программисты часто сталкиваются с необходимостью выгрузки данных во внешние приложения, такие как Microsoft Excel. Одной из типичных проблем, возникающих при написании сложных циклов обработки данных, является ситуация, когда выборка запроса ведет себя «странно»: в одном случае данные перебираются корректно, а в другом — код бесконечно (или многократно) обращается к одной и той же записи. Рассмотрим подробнее, почему это происходит, и разберем фундаментальные механизмы работы с объектами Запрос и ВыборкаИзРезультатаЗапроса.

Анализ ситуации: Разница между работающим и неработающим кодом

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

Основная ошибка заключалась в том, что внутри циклов Пока Выборка.Следующий() данные считывались не из текущей (свежей) выборки Выборка, а из старой переменной ВыборкаДетальныеЗаписи, которая осталась от предыдущего выполнения кода. Выясним причину, почему это приводит к «зацикливанию» на одном сотруднике.

Причина №1: Состояние указателя в объекте выборки

Когда мы вызываем метод РезультатЗапроса.Выбрать(), система создает объект ВыборкаИзРезультатаЗапроса. Этот объект содержит внутри себя «курсор» (указатель), который изначально стоит перед первой записью. Метод Следующий() сдвигает этот курсор на одну позицию вперед.

Рассмотрим, что произошло в неработающем коде автора:

  1. Первый цикл полностью прошел по всей выборке ВыборкаДетальныеЗаписи.
  2. После завершения цикла указатель в ВыборкаДетальныеЗаписи остановился на последней записи.
  3. Затем автор создает новую выборку в переменную Выборка и начинает цикл по ней.
  4. Внутри нового цикла автор пишет: Лист.Cells(СТР, 1).Value = ВыборкаДетальныеЗаписи.Сотрудник.

Поскольку ВыборкаДетальныеЗаписи больше не перемещается (ее метод Следующий() в этом блоке не вызывается), она всегда возвращает данные той самой последней записи, на которой остановилась ранее. Программа честно выполняет цикл столько раз, сколько строк в новой выборке, но значения каждый раз берет из старой. Именно поэтому в таблицу Excel записывался один и тот же сотрудник много раз.

Причина №2: Копирование объектов по ссылке

В коде автора встречается строка: Запрос3 = Запрос;. Это критически важный момент для понимания архитектуры 1С. В языке 1С сложные объекты (запросы, массивы, таблицы значений) передаются и присваиваются по ссылке, а не по значению.

Проанализируем ситуацию: когда вы пишете Запрос3 = Запрос, вы не создаете новый независимый объект. Вы просто создаете вторую «переменную-указатель», которая смотрит на тот же самый экземпляр запроса в памяти. Если вы измените Запрос3.Текст, то текст изменится и у оригинального объекта Запрос. Это может привести к непредсказуемым результатам в дальнейшей работе программы, если вы рассчитываете, что в исходной переменной сохранился старый текст запроса.

Правильный подход для создания копии (если это действительно необходимо) — инициализация нового объекта:


Запрос3 = Новый Запрос;
Запрос3.Текст = Запрос.Текст; // Копируем текст
// Копируем параметры, если нужно
Для Каждого Параметр Из Запрос.Параметры Цикл
    Запрос3.УстановитьПараметр(Параметр.Ключ, Параметр.Значение);
КонецЦикла;

Как исправить код: Правильная работа с выборками

Чтобы код заработал, необходимо синхронизировать переменную, по которой идет цикл, и переменную, из которой считываются данные. Посмотрим на исправленный фрагмент кода:


// Выполняем измененный запрос
Рез = Запрос3.Выполнить();
Выборка = Рез.Выбрать();

Пока Выборка.Следующий() Цикл
    // Используем данные из ТЕКУЩЕЙ выборки, а не из старой!
    ЛистРазрез3.Cells(СТР, 1).Value = Строка(Выборка.Сотрудник); 
    ЛистРазрез3.Cells(СТР, 2).Value = Число(Выборка[МесяцМинус3]);
    СТР = СТР + 1;
КонецЦикла;

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

Рекомендации по оптимизации: Работа с Excel (COM)

Хотя исправление имен переменных решает проблему «одного сотрудника», сам метод работы через Excel.Application (COM-объект) внутри циклов запроса является крайне медленным. Рассмотрим, как можно улучшить производительность.

Каждое обращение к Cells(СТР, Колонка).Value инициирует межпроцессное взаимодействие между 1С и Excel, что занимает значительное время. Если у вас 1000 сотрудников и 12 месяцев, это тысячи вызовов. Проанализируем более эффективные методы:

  1. Выгрузка в Таблицу Значений: Сначала выгрузите результат запроса в объект ТаблицаЗначений методом РезультатЗапроса.Выгрузить(). С таблицей в памяти 1С работает в десятки раз быстрее, чем с выборкой.
  2. Использование массивов: В COM-технологии Excel есть возможность передать двумерный массив данных в диапазон ячеек одним вызовом. Это работает мгновенно. Вместо того чтобы заполнять ячейки по одной, мы создаем массив в 1С, заполняем его данными и присваиваем свойству Value диапазона Range.
  3. Отказ от COM в пользу XML/XLSX: Современные версии 1С позволяют сохранять табличные документы напрямую в формате .xlsx без установленного Excel на сервере, используя метод ТабличныйДокумент.Записать().

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

Рассмотрим пример, который объединяет правильную логику работы с данными и подготовку к эффективной выгрузке:


Запрос = Новый Запрос(ТекстЗапроса);
// Установка параметров...
Результат = Запрос.Выполнить();

// Вместо мучительного перебора выборки выгружаем всё в ТЗ
ТаблицаДанных = Результат.Выгрузить();

// Теперь мы можем работать с данными ТЗ, искать в ней строки, 
// сортировать или группировать без повторных запросов к БД
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
    // Пример поиска соответствия в Excel (хотя лучше заполнять Excel из ТЗ)
    // РаздельноФИО = СтрРазделить(СтрокаДанных.Сотрудник, " ");
    // ... логика заполнения ...
КонецЦикла;

Выводы

Подводя итог, выделим три правила, которые помогут избежать подобных ошибок в будущем:

Во-первых, всегда проверяйте соответствие переменной в условии цикла Пока ... Следующий() и переменных, используемых внутри тела цикла. Ошибки копирования (Copy-Paste) — самый частый источник «замерших» данных.

Во-вторых, помните о ссылочной природе объектов 1С. Изменение «копии» запроса, созданной через простое равенство, меняет оригинал.

В-третьих, используйте инструменты отладки. Если вы видите, что данные не меняются, поставьте точку останова внутри цикла и посмотрите в «Табло», какое значение имеет переменная выборки и на какой строке она находится (свойство НомерСтроки).

Соблюдение этих принципов сделает ваш код более надежным, а процесс разработки — предсказуемым.

← На главную