Почему цикл обхода таблицы значений после запроса не выполняется и как это исправить?

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

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

Разбор технической причины: Инструкция ПОМЕСТИТЬ и результат пакета

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

Основная ошибка заключается в понимании того, что возвращает инструкция ПОМЕСТИТЬ. Когда мы пишем запрос вида:


Выбрать
    ВнешняяТЗ.Номенклатура Как Номенклатура,
    ВнешняяТЗ.КодНоменклатуры Как КодНоменклатуры
Поместить ВременнаяТаблицаНоменклатуры
Из &ВнешняяТЗ Как ВнешняяТЗ
Где ВнешняяТЗ.КодНоменклатуры В(&МассивКодов)

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

Выясним причину: Инструкция ПОМЕСТИТЬ предназначена для сохранения данных внутри сессии запроса для дальнейшего использования в соединениях (JOIN) или последующих выборках. Она возвращает состояние операции, а не сами отобранные записи.

Решение №1: Добавление финальной выборки в пакет

Чтобы получить данные обратно в контекст встроенного языка 1С, мы должны явно "попросить" систему вернуть содержимое временной таблицы. Для этого пакет запросов должен завершаться инструкцией ВЫБРАТЬ. Исправленный текст запроса будет выглядеть следующим образом:


Запрос.Текст = "ВЫБРАТЬ
               |    ВнешняяТЗ.Номенклатура КАК Номенклатура,
               |    ВнешняяТЗ.КодНоменклатуры КАК КодНоменклатуры,
               |    ВнешняяТЗ.КодЕдиницИзмерения КАК КодЕдиниц,
               |    ВнешняяТЗ.Штрихкод КАК Штрихкод
               |ПОМЕСТИТЬ ВтДанные
               |ИЗ &ВнешняяТЗ КАК ВнешняяТЗ
               |ГДЕ ВнешняяТЗ.КодНоменклатуры В(&МассивКодов);
               |
               |////////////////////////////////////////////////////////////////////////////////
               |ВЫБРАТЬ
               |    ВтДанные.Номенклатура,
               |    ВтДанные.КодНоменклатуры,
               |    ВтДанные.КодЕдиниц,
               |    ВтДанные.Штрихкод
               |ИЗ ВтДанные КАК ВтДанные";

РезультатПакета = Запрос.ВыполнитьПакет();
// Если мы добавили выборку в конец, нам нужен последний элемент массива результатов
ТаблицаНоменклатуры = РезультатПакета[РезультатПакета.Количество() - 1].Выгрузить();

В этом случае в ТаблицаНоменклатуры попадут именно те строки, которые соответствуют условию фильтрации. Теперь цикл Для Каждого Строка Из ТаблицаНоменклатуры Цикл будет успешно выполняться.

Решение №2: Оптимизация — работа без временных таблиц

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


Запрос.Текст = "ВЫБРАТЬ
               |    ВнешняяТЗ.Номенклатура КАК Номенклатура,
               |    ВнешняяТЗ.КодНоменклатуры КАК КодНоменклатуры,
               |    ВнешняяТЗ.КодЕдиницИзмерения КАК КодЕдиниц,
               |    ВнешняяТЗ.Штрихкод КАК Штрихкод
               |ИЗ &ВнешняяТЗ КАК ВнешняяТЗ
               |ГДЕ ВнешняяТЗ.КодНоменклатуры В(&МассивКодов)";

ТаблицаНоменклатуры = Запрос.Выполнить().Выгрузить();

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

Важный нюанс: Типизация колонок внешней таблицы значений

Одной из причин, по которой запрос к внешней таблице значений возвращает 0 строк (и, как следствие, цикл пропускается), является отсутствие явной типизации колонок. Это особенно актуально при загрузке данных из Excel или DBF.

Разберем, как это работает: когда мы передаем таблицу значений в запрос через УстановитьПараметр("ВнешняяТЗ", ТаблицаЗапроса), 1С должна создать временную таблицу в базе данных. Если у колонок исходной таблицы не задан тип (тип Произвольный), система пытается определить его по первой строке. Если первая строка пуста или содержит данные в неочевидном формате, типы могут определиться неверно. Например, код номенклатуры в массиве — Строка, а в созданной временной таблице — Число. Условие ГДЕ ... В (&Массив) никогда не сработает, так как типы не совпадут.

Рекомендуемый алгоритм подготовки таблицы значений:

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

Пример правильного создания таблицы:


МассивТиповСтрока = Новый Массив;
МассивТиповСтрока.Добавить(Тип("Строка"));
ОписаниеТиповСтрока = Новый ОписаниеТипов(МассивТиповСтрока, , Новый КвалификаторыСтроки(20));

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

Почему поведение отличается на управляемых и обычных формах?

Рассмотрим вопрос автора о разнице в поведении форм. На управляемых формах код выполняется строго на сервере. На обычных формах (особенно в толстом клиенте) выполнение может происходить на стороне клиента. При передаче таблицы значений с клиента на сервер для выполнения запроса происходит сериализация данных. Если типы колонок не определены, механизм передачи данных может "потерять" содержимое или интерпретировать его некорректно, что и приводит к пустому результату запроса на обычных формах.

Альтернативный подход без использования запроса

Как справедливо заметили участники обсуждения, если данных немного, можно обойтись без механизма запросов, что упростит отладку и поддержку кода. Рассмотрим вариант использования метода Найти() для массива или метода НайтиСтроки() для таблицы значений:


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

    // Далее логика поиска в справочнике и создания элементов
    НоменклатураСсылка = Справочники.Номенклатура.НайтиПоКоду(СокрЛП(СтрокаТЗ.КодНоменклатуры));
    Если НоменклатураСсылка.Пустая() Тогда
        // Создание новой номенклатуры
        НовыйОбъект = Справочники.Номенклатура.СоздатьЭлемент();
        // ... заполнение реквизитов ...
        НовыйОбъект.Записать();
    КонецЕсли;
КонецЦикла;

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

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

← На главную