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