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