Язык запросов 1С

Ошибка «Тип не может быть выбран в запросе» в 1С 8.3: типизация ТаблицыЗначений

Коротко

Ошибка {(X, Y)}: Тип не может быть выбран в запросе при помещении ТаблицаЗначений во временную таблицу (ВЫБРАТЬ * ПОМЕСТИТЬ ВТ ИЗ &ТЗ КАК ТЗ) возникает по трём причинам: у колонки не задано ОписаниеТипов, для строковой колонки не указана фиксированная ненулевая длина в КвалификаторыСтроки(Длина) либо в колонке лежит прикладной тип, который СУБД не может разместить во временной таблице (Структура, Соответствие, Массив, ХранилищеЗначения, ДокументОбъект).

Чтобы запрос выполнился без ошибок, создавайте колонки через ТЗ.Колонки.Добавить("Имя", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150))), для чисел задавайте Новый КвалификаторыЧисла(15, 2), а перед вызовом Запрос.Выполнить() инициализируйте Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц.

Почему возникает ошибка «Тип не может быть выбран в запросе»

При выполнении пакета с конструкцией ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ ИЗ &ТЗ КАК ТЗ платформа 1С:Предприятие 8.3 транслирует переданную в параметр коллекцию в реальную временную таблицу СУБД (например, #tt1 в MS SQL Server или временную таблицу в PostgreSQL). Для команды CREATE TABLE серверу баз данных нужны точные SQL-типы каждого выбираемого поля: NUMERIC(15, 2), NVARCHAR(100), BINARY(16) для ссылок или DATETIME.

Если разработчик создал колонку вызовом ТЗ.Колонки.Добавить("Номенклатура") без второго параметра или получил ТаблицаЗначений из внешней обработки без типизации, свойство Колонка.ТипЗначения содержит пустое ОписаниеТипов (произвольный тип). Платформа не знает, какую структуру колонок создавать в СУБД, и на строке Запрос.Выполнить() выбрасывает исключение:

Ошибка при вызове метода контекста (Выполнить):
{(2, 2)}: Тип не может быть выбран в запросе
<<?>>ТЗ.Артикул КАК Артикул,

Координаты {(X, Y)} в тексте исключения всегда указывают на номер строки и позицию поля в секции ВЫБРАТЬ, тип которого не удалось отобразить в тип поля временной таблицы.

// ПРОБЛЕМНЫЙ КОД: вызовет исключение на Запрос.Выполнить()
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Артикул"); // Тип не задан (Произвольный)
ТЗ.Колонки.Добавить("Комментарий", Новый ОписаниеТипов("Строка")); // Длина = 0!

СтрокаТЗ = ТЗ.Добавить();
СтрокаТЗ.Артикул = "АРТ-00125";
СтрокаТЗ.Комментарий = "Поставка по договору";

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
    "ВЫБРАТЬ
    |   ТЗ.Артикул КАК Артикул,
    |   ТЗ.Комментарий КАК Комментарий
    |ПОМЕСТИТЬ ВТ_Данные
    |ИЗ
    |   &ТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Запрос.Выполнить(); // Ошибка: Тип не может быть выбран в запросе
КолонкиТаблицыЗначений.Добавить
ИмяСтрока — идентификатор колонки для обращения в запросе
ТипОписаниеТипов — без него создаётся произвольный тип, запрещённый в ПОМЕСТИТЬ ВТ
ЗаголовокСтрока — заголовок колонки
ШиринаЧисло — ширина в символах
→ КолонкаТаблицыЗначений

Два жёстких требования к колонкам ТаблицыЗначений

Чтобы таблица значений без ошибок прошла через ПОМЕСТИТЬ ВТ, каждая выбираемая в запросе колонка должна одновременно выполнять два условия платформы 1С 8.3:

  1. Задан конкретный ОписаниеТипов. В Колонка.ТипЗначения должен быть указан один примитивный тип, конкретная ссылка (например, СправочникСсылка.Номенклатура) или допустимый составной тип из ссылок и примитивов. Значение по умолчанию (пустой конструктор Новый ОписаниеТипов()) не допускается.
  2. Для типа Строка задана ненулевая фиксированная длина в КвалификаторыСтроки. Когда вы пишете Новый ОписаниеТипов("Строка") без квалификаторов, платформа создаёт описание строки с КвалификаторыСтроки.Длина = 0 (неограниченная длина, аналог MEMO / NVARCHAR(MAX) / TEXT). В языке запросов 1С поля неограниченной длины нельзя помещать во временные таблицы, группировать и индексировать, поэтому на этапе ПОМЕСТИТЬ ВТ возникает та же самая ошибка Тип не может быть выбран в запросе.
ТЗ = Новый ТаблицаЗначений;

// 1. Строка: ОБЯЗАТЕЛЬНО указываем ненулевую длину в КвалификаторыСтроки
КвСтроки100 = Новый КвалификаторыСтроки(100, ДопустимаяДлина.Переменная);
ТЗ.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка", , КвСтроки100));

// 2. Число: указываем разрядность и точность в КвалификаторыЧисла
КвЧисла = Новый КвалификаторыЧисла(15, 3, ДопустимыйЗнак.Любой);
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", КвЧисла));

// 3. Дата: указываем состав даты (Дата, Время или ДатаВремя)
КвДаты = Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя);
ТЗ.Колонки.Добавить("ДатаПоставки", Новый ОписаниеТипов("Дата", , , КвДаты));

// 4. Булево и конкретная ссылка: квалификаторы не требуются
ТЗ.Колонки.Добавить("ЭтоУслуга", Новый ОписаниеТипов("Булево"));
ТЗ.Колонки.Добавить("Номенклатура",
    Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));

// 5. Составной тип документа (несколько ссылочных типов)
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("ДокументСсылка.РеализацияТоваровУслуг"));
МассивТипов.Добавить(Тип("ДокументСсылка.ПоступлениеТоваровУслуг"));
ТЗ.Колонки.Добавить("ДокументОснование", Новый ОписаниеТипов(МассивТипов));
Новый ОписаниеТипов(...)
ТипыСтрока с именами типов через запятую или Массив из Тип
КвалификаторыЧислаКвалификаторыЧисла(Разрядность, Точность, Знак) — 2-й параметр
КвалификаторыСтрокиКвалификаторыСтроки(Длина, ДопустимаяДлина) — 3-й параметр, Длина > 0
КвалификаторыДатыКвалификаторыДаты(ЧастиДаты) — 4-й параметр
→ ОписаниеТипов

Важно учитывать, что если у колонки ТаблицаЗначений длина строки равна 0 (Новый ОписаниеТипов("Строка")) или тип колонки произвольный, то обёртка ВЫРАЗИТЬ(ТЗ.Комментарий КАК СТРОКА(250)) при чтении ИЗ &ТЗ ошибку не устранит — платформа проверяет типы колонок внешнего источника до вычисления выражений выборки. Оператор ВЫРАЗИТЬ(... КАК СТРОКА(250)) нужно применять на шаг раньше — в том запросе к базе данных, результат которого выгружается в ТЗ через Выгрузить(), либо исправлять Колонка.ТипЗначения в коде 1С перед передачей параметра.

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

Какие типы вообще нельзя выбирать в запросе из ТаблицыЗначений

Даже если вы аккуратно создали Новый ОписаниеТипов(...) для каждой колонки, язык запросов 1С 8.3 поддерживает только те типы данных, которые имеют прямое представление в таблицах базы данных. Прикладные коллекции, мутабельные объекты и бинарные контейнеры выбрать в секции ВЫБРАТЬ из источника &ТЗ нельзя.

Категория типа в колонке ТЗ Примеры типов 1С 8.3 Допустимость в ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ Как правильно передать в запрос
Примитивные типы (с квалификаторами) Число, Строка(N) (N > 0), Дата, Булево Полностью поддерживаются Задать КвалификаторыЧисла и КвалификаторыСтроки(N) при создании колонки
Ссылочные типы и перечисления СправочникСсылка.*, ДокументСсылка.*, ПеречислениеСсылка.*, ПланВидовХарактеристикСсылка.* Полностью поддерживаются Указать точное имя типа метаданных в ОписаниеТипов
Системные идентификаторы УникальныйИдентификатор, МоментВремени, Граница В ВЫБРАТЬ не выбираются (вызовут ошибку типа) Заранее преобразовывать в ссылку через Справочники.Номенклатура.ПолучитьСсылку(УИД) или передавать в ТЗ как Строка(36) (а в запросе к БД получать функцией УНИКАЛЬНЫЙИДЕНТИФИКАТОР(Ссылка))
Прикладные объекты (в памяти) СправочникОбъект.*, ДокументОбъект.*, ОтчетОбъект.*, ОбработкаОбъект.* Запрещены Заменить в колонке объект на его ссылку: СтрокаТЗ.Документ = ДокОбъект.Ссылка
Коллекции и контейнеры Структура, Соответствие, Массив, СписокЗначений, ТаблицаЗначений, ХранилищеЗначения, ДвоичныеДанные Запрещены Удалить служебную колонку из ТЗ или не включать её в список полей ВЫБРАТЬ (отказаться от ВЫБРАТЬ *)
Описатели типов Тип, ОписаниеТипов, СтандартнаяДатаНачала, СтандартныйПериод Запрещены в колонках источника ВТ Передавать отдельным параметром запроса (&НужныйТип) или строковым именем

Обратите внимание на важный нюанс: если в вашей ТаблицаЗначений 10 колонок, и в 9-й колонке лежит Структура или ХранилищеЗначения для клиентской логики формы, вам не обязательно удалять эту колонку из ТЗ. Достаточно в тексте запроса явно перечислить только нужные типизированные колонки (ВЫБРАТЬ ТЗ.Номенклатура, ТЗ.Количество ПОМЕСТИТЬ ВТ ИЗ &ТЗ КАК ТЗ) вместо использования звёздочки ВЫБРАТЬ *.

Ошибка при передаче Массива во временную таблицу

Исключение Тип не может быть выбран в запросе возникает не только с ТаблицаЗначений, но и при попытке поместить во временную таблицу обычный Массив конструкцией (при этом СписокЗначений передавать в секцию ИЗ &Список нельзя вообще — он допустим только в условии В (&Список)):

ВЫБРАТЬ
    Список.Значение КАК Номенклатура
ПОМЕСТИТЬ ВТ_Фильтр
ИЗ
    &МассивНоменклатуры КАК Список

В отличие от ТаблицаЗначений, у объекта Массив в 1С 8.3 нет описания типов колонок. Платформа определяет тип единственного поля Значение по фактическим элементам массива. Ошибка появляется в трёх случаях:

  • Передан пустой массив (Массив.Количество() = 0). Внутри нет ни одного элемента, поэтому платформа не может вывести тип поля Значение и отказывается создавать временную таблицу.
  • В массиве лежат строки или числа. У примитивных элементов массива нет фиксированных квалификаторов длины и разрядности, поэтому 1С трактует их как Строка(0) / Число(0) неограниченной длины.
  • В массиве есть хотя бы одно значение Неопределено или Null.

Поскольку у пустого массива и массива строк нет квалификаторов типа на уровне источника, конструкция ВЫРАЗИТЬ(Список.Значение КАК ...) при чтении ИЗ &МассивНоменклатуры от падения не защитит. Надёжное решение — вместо Массива передавать во временную таблицу одноколоночную типизированную ТаблицаЗначений с явно заданным ОписаниеТипов.

Готовая универсальная функция подготовки и проверки ТаблицыЗначений

На практике в запрос часто передают ТаблицаЗначений, которая пришла из результата выгрузки табличной части, чтения файла Excel/JSON или метода другой подсистемы. Ниже приведена готовая серверная функция, которая проверяет каждую колонку ТаблицаЗначений, автоматически выводит ОписаниеТипов по фактическим данным (если тип не был задан или для строки установлена нулевая длина) и гарантированно подготавливает таблицу к передаче в Запрос.УстановитьПараметр("ТЗ", ТЗ) и МенеджерВременныхТаблиц.

// Проверяет и при необходимости типизирует колонки ТаблицыЗначений
// перед передачей в пакетный запрос с ПОМЕСТИТЬ ВТ.
Функция ПодготовитьТЗДляЗапроса(ИсходнаяТЗ, ДлинаСтрокиПоУмолчанию = 250) Экспорт

    ТЗ = ИсходнаяТЗ.Скопировать();
    КвСтроки = Новый КвалификаторыСтроки(ДлинаСтрокиПоУмолчанию, ДопустимаяДлина.Переменная);
    КвЧисла  = Новый КвалификаторыЧисла(15, 3, ДопустимыйЗнак.Любой);
    КвДаты   = Новый КвалификаторыДаты(ЧастиДаты.ДатаВремя);

    Для Каждого Колонка Из ТЗ.Колонки Цикл
        Описание = Колонка.ТипЗначения;
        ТипыКолонки = Описание.Типы();

        // Случай 1: тип колонки вообще не задан (Произвольный)
        Если ТипыКолонки.Количество() = 0 Тогда
            МассивНайденныхТипов = Новый Массив;
            Для Каждого СтрокаТЗ Из ТЗ Цикл
                Знач = СтрокаТЗ[Колонка.Имя];
                Если Знач <> Неопределено И Знач <> Null Тогда
                    ТипЗнач = ТипЗнч(Знач);
                    Если МассивНайденныхТипов.Найти(ТипЗнач) = Неопределено Тогда
                        МассивНайденныхТипов.Добавить(ТипЗнач);
                    КонецЕсли;
                КонецЕсли;
            КонецЦикла;

            Если МассивНайденныхТипов.Количество() = 0 Тогда
                // Если колонка полностью пустая — задаём Строка(Длина), чтобы запрос не упал
                МассивНайденныхТипов.Добавить(Тип("Строка"));
            КонецЕсли;

            Колонка.ТипЗначения = Новый ОписаниеТипов(
                МассивНайденныхТипов, КвЧисла, КвСтроки, КвДаты);

        // Случай 2: тип задан, но у Строки Длина = 0 или у Числа Разрядность = 0
        Иначе
            НовыеКвЧисла = ?(Описание.СодержитТип(Тип("Число"))
                И Описание.КвалификаторыЧисла.Разрядность = 0, КвЧисла, Неопределено);
            НовыеКвСтроки = ?(Описание.СодержитТип(Тип("Строка"))
                И Описание.КвалификаторыСтроки.Длина = 0, КвСтроки, Неопределено);
            Если НовыеКвЧисла <> Неопределено Или НовыеКвСтроки <> Неопределено Тогда
                Колонка.ТипЗначения = Новый ОписаниеТипов(
                    Описание, , , НовыеКвЧисла, НовыеКвСтроки);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;

    Возврат ТЗ;

КонецФункции
ОписаниеТипов (на основании)
ИсходноеОписаниеТиповОписаниеТипов — базовое описание колонки
ДобавляемыеТипыМассив или Строка добавляемых типов
ВычитаемыеТипыМассив или Строка исключаемых типов
КвалификаторыЧисла / Строки / Даты4-й, 5-й и 6-й параметры — заменяют квалификаторы исходного описания
→ ОписаниеТипов

Пример вызова подготовленной таблицы вместе с МенеджерВременныхТаблиц:

ТипизированнаяТЗ = ПодготовитьТЗДляЗапроса(ВходящаяТЗ, 150);

МВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МВТ;
Запрос.Текст =
    "ВЫБРАТЬ
    |   ТЗ.Номенклатура КАК Номенклатура,
    |   ТЗ.Артикул КАК Артикул,
    |   ТЗ.Количество КАК Количество
    |ПОМЕСТИТЬ ВТ_Товары
    |ИЗ
    |   &ТЗ КАК ТЗ
    |
    |ИНДЕКСИРОВАТЬ ПО
    |   Номенклатура
    |;
    |
    |ВЫБРАТЬ
    |   ВТ.Номенклатура КАК Номенклатура,
    |   ВТ.Количество КАК КоличествоПоЗаявке,
    |   ЕСТЬNULL(Остатки.ВНаличииОстаток, 0) КАК ОстатокНаСкладе
    |ИЗ
    |   ВТ_Товары КАК ВТ
    |       ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
    |       ПО ВТ.Номенклатура = Остатки.Номенклатура";

Запрос.УстановитьПараметр("ТЗ", ТипизированнаяТЗ);
Выборка = Запрос.Выполнить().Выбрать();

Типизация при Выгрузить() из результата запроса и реквизита формы

Частый вопрос разработчиков: нужно ли вручную типизировать ТаблицаЗначений, если она получена методом РезультатЗапроса.Выгрузить() или методом РеквизитФормыВЗначение("Товары").Выгрузить()?

  • После РезультатЗапроса.Выгрузить(): все колонки, полученные из полей базы данных (реквизиты справочников, документов, измерения и ресурсы регистров), уже имеют строгие ОписаниеТипов с ненулевыми квалификаторами. Однако если в первом запросе было вычисляемое строковое выражение (например, конкатенация Поле1 + " / " + Поле2 без ВЫРАЗИТЬ(... КАК СТРОКА(100))) или поле со значением Неопределено, то в выгруженной ТЗ эта колонка получит неограниченную длину строки либо произвольный тип, и при передаче во второй запрос упадёт с ошибкой.
  • После ТабличнаяЧасть.Выгрузить() или ДанныеФормыКоллекция.Выгрузить(): колонки, соответствующие реквизитам табличной части метаданных, сохраняют свои типы. Но если на управляемой форме в таблицу были программно добавлены дочерние колонки без указания квалификаторов строки, перед передачей в запрос их нужно либо исключить из списка ВЫБРАТЬ, либо пропустить через ПодготовитьТЗДляЗапроса().
Отлаживать пакетные запросы с временными таблицами и проверять планы соединений на реальных данных быстрее в ИИ Консоли запросов 1С. Подробное сравнение возможностей доступно на странице тарифов ИИ Консоли запросов 1С.

Частые ошибки при передаче ТаблицыЗначений в запрос

  • Использование Новый ОписаниеТипов("Строка") без третьего параметра. Самая распространённая ловушка: разработчик уверен, что типизировал колонку, но без Новый КвалификаторыСтроки(Длина) длина строки равна 0, что запрещено для временных таблиц запроса.
  • Перепутан порядок параметров в конструкторе ОписаниеТипов. В платформе 1С 8.3 вторым параметром идут КвалификаторыЧисла, а КвалификаторыСтроки — третьим. Если забыть лишнюю запятую и написать Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(50)), платформа выдаст ошибку несоответствия типов параметров конструктора.
  • Использование ВЫБРАТЬ * из таблицы со служебными колонками. Если в ТаблицаЗначений есть хотя бы одна нетипизированная колонка или колонка со Структура, запрос ВЫБРАТЬ * ПОМЕСТИТЬ ВТ ИЗ &ТЗ упадёт. Всегда явно перечисляйте только те колонки ТЗ, которые участвуют в запросе.
  • Передача колонки с типом УникальныйИдентификатор. Несмотря на то что УникальныйИдентификатор — системный тип платформы, язык запросов 1С не умеет выбирать его в секции ВЫБРАТЬ. Передавайте UUID в ТЗ как Строка(36) или сразу передавайте СправочникСсылка.
  • Передача ДокументОбъект или СправочникОбъект вместо ссылки. Если при заполнении строки ТЗ присвоить СтрокаТЗ.Документ = ДокОбъект вместо ДокОбъект.Ссылка, при попытке выбрать поле в запросе возникнет ошибка Тип не может быть выбран в запросе.
  • Забыли МенеджерВременныхТаблиц при раздельном выполнении запросов. Если первый запрос выполняет только ВЫБРАТЬ ... ПОМЕСТИТЬ ВТ ИЗ &ТЗ без последующего пакета, а свойство Запрос.МенеджерВременныхТаблиц не заполнено, платформа выбросит ошибку Не установлен менеджер временных таблиц.

Частые вопросы по ошибке «Тип не может быть выбран в запросе»

Почему ошибка «Тип не может быть выбран в запросе» возникает, даже если колонка типизирована как Строка?

Потому что вызов Новый ОписаниеТипов("Строка") без квалификаторов создаёт строку неограниченной длины (Длина = 0). СУБД не может создать колонку временной таблицы без фиксированного предела длины, поэтому нужно передать третий параметр: Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)).

Можно ли исправить ошибку прямо в тексте запроса через оператор ВЫРАЗИТЬ без изменения кода 1С?

Если таблица значений уже передана в секцию ИЗ &ТЗ с нетипизированной колонкой или строкой неограниченной длины (Длина = 0), оператор ВЫРАЗИТЬ(ТЗ.Поле КАК СТРОКА(100)) в этом же запросе не поможет — платформа проверяет типы колонок внешнего источника до выполнения выражений. Оператор ВЫРАЗИТЬ помогает на этапе формирования самой ТЗ запросом к базе данных (например, ВЫРАЗИТЬ(Поле1 + Поле2 КАК СТРОКА(150)) перед Выгрузить()), а для готовой ТЗ необходимо задать ОписаниеТипов колонки в коде 1С.

Как передать в запрос ТаблицуЗначений, если она пустая (0 строк)?

Пустая ТаблицаЗначений передаётся во временную таблицу без ошибок, если у всех её выбираемых колонок заранее задан корректный ОписаниеТипов с ненулевыми квалификаторами строк и чисел. Платформа создаст пустую временную таблицу с нужной структурой полей, и последующие соединения в пакете запросов отработают штатно.