При разработке на платформе 1С:Предприятие 8.3 мы часто сталкиваемся с необходимостью динамического формирования данных и их последующей обработки в запросах. Один из распространенных подходов — это создание таблицы значений программно, заполнение ее данными и передача в запрос в качестве параметра. Однако, когда в такой таблице значений присутствует колонка, имеющая составной тип (например, может быть ссылкой на документ одного из нескольких видов), мы можем столкнуться с ошибкой "Тип не может быть выбран в запросе". Давайте рассмотрим подробнее, почему возникает эта проблема и как ее корректно решить.
Представим ситуацию: мы программно создали таблицу значений (ТаблицаДанных), добавили в нее колонку, например, ДокументОтгрузки, которая должна хранить ссылки как на ДокументСсылка.РеализацияТоваровУслуг, так и на ДокументСсылка.ВозвратТоваровОтПокупателя. При попытке передать такую таблицу в запрос и выбрать это поле напрямую, платформа 1С выдает ошибку: "Тип не может быть выбран в запросе". Эта ошибка возникает потому, что система запросов не может однозначно определить, какой именно тип из возможных компонентов составного типа должен быть выбран, когда мы указываем поле без дополнительных уточнений.
Давайте проанализируем некорректный способ описания типов, который часто приводит к этой ошибке. Обычно, пытаясь описать составной тип, разработчики могут по ошибке сделать следующее:
МассивОписанийТипов = Новый Массив;
МассивОписанийТипов.Добавить(Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг"));
МассивОписанийТипов.Добавить(Новый ОписаниеТипов("ДокументСсылка.ВозвратТоваровОтПокупателя"));
ТаблицаДанных.Колонки.Добавить("ДокументОтгрузки", Новый ОписаниеТипов(МассивОписанийТипов));
В этом примере мы создаем массив, но вместо того, чтобы добавить в него сами объекты Тип, мы добавляем в него другие объекты ОписаниеТипов. То есть, мы формируем "массив описаний типов", а затем оборачиваем его еще в один объект ОписаниеТипов. Такая вложенность является некорректной для указания составного типа колонки таблицы значений.
Кроме того, причина может быть в наличии Null значений в полях составного типа без явного описания Тип("Неопределено"), или присутствии типов, которые не могут быть выбраны в запросе (например, объекты Картинка, Диаграмма, Шрифт и другие нессылочные типы, которые не поддерживаются в запросах без специальной обработки).
Для того чтобы избежать ошибки "Тип не может быть выбран в запросе", крайне важно правильно описать составной тип колонки нашей таблицы значений. Существует два основных корректных способа, которые мы можем использовать:
Использование массива объектов Тип(): Вместо создания массива объектов ОписаниеТипов, мы должны создать массив, содержащий сами объекты Тип, которые можно получить с помощью функции Тип().
Рассмотрим пример:
// Вариант 1: Использование массива объектов Тип()
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("ДокументСсылка.РеализацияТоваровУслуг"));
МассивТипов.Добавить(Тип("ДокументСсылка.ВозвратТоваровОтПокупателя"));
ТаблицаДанных.Колонки.Добавить("ДокументОтгрузки", Новый ОписаниеТипов(МассивТипов));
Здесь мы видим, что в массив МассивТипов добавляются непосредственно ссылки на типы документов, а не объекты ОписаниеТипов. Затем этот массив передается в конструктор Новый ОписаниеТипов(), что позволяет платформе корректно распознать составной тип колонки.
Использование строки с перечислением типов через запятую: Это более краткий и часто используемый способ. Мы можем просто передать в конструктор Новый ОписаниеТипов() строку, содержащую имена всех возможных типов, разделенных запятыми.
Пример такого подхода:
// Вариант 2: Использование строки с перечислением типов через запятую
ТаблицаДанных.Колонки.Добавить("ДокументОтгрузки", Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг, ДокументСсылка.ВозвратТоваровОтПокупателя"));
Этот вариант является наиболее лаконичным и функционально эквивалентен первому. Он также позволяет корректно определить составной тип для колонки.
Важно: Если среди возможных значений колонки может быть Неопределено, необходимо явно включить его в описание типов: Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг, ДокументСсылка.ВозвратТоваровОтПокупателя, Неопределено").
После того как мы правильно описали типы колонок нашей таблицы значений, мы можем без проблем передать ее в запрос как параметр и поместить во временную таблицу. Для этого мы будем использовать объект Запрос и его свойство МенеджерВременныхТаблиц. Для расширения возможностей работы с динамическими данными также пригодится механизм добавления данных в существующие временные таблицы.
Рассмотрим пример, который показывает, как передать таблицу значений с корректно описанным составным типом в запрос:
// Создаем таблицу значений
ТЗ = Новый ТаблицаЗначений;
// Добавляем колонку с составным типом (используем любой из двух корректных вариантов)
ТЗ.Колонки.Добавить("ДокументОтгрузки", Новый ОписаниеТипов("ДокументСсылка.РеализацияТоваровУслуг, ДокументСсылка.ВозвратТоваровОтПокупателя"));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число")); // Для примера добавим еще одну колонку
// Заполняем таблицу значений тестовыми данными
Для Каждого ДокументТек Из Документы.РеализацияТоваровУслуг.Выбрать() Цикл
Если ДокументТек.Ссылка.Пустая() Тогда Продолжить КонецЕсли;
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.ДокументОтгрузки = ДокументТек.Ссылка;
НоваяСтрока.Сумма = ДокументТек.СуммаДокумента;
Прервать; // Для примера одной строки
КонецЦикла;
Для Каждого ДокументТек Из Документы.ВозвратТоваровОтПокупателя.Выбрать() Цикл
Если ДокументТек.Ссылка.Пустая() Тогда Продолжить КонецЕсли;
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.ДокументОтгрузки = ДокументТек.Ссылка;
НоваяСтрока.Сумма = ДокументТек.СуммаДокумента;
Прервать; // Для примера одной строки
КонецЦикла;
// Формируем текст запроса
ТекстЗапроса =
"ВЫБРАТЬ
| Т.ДокументОтгрузки КАК ДокументОтгрузки,
| Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВремТаблицаОтгрузок
|ИЗ
| &ТаблицаДанных КАК Т";
// Создаем объект Запрос и устанавливаем менеджер временных таблиц
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Устанавливаем нашу таблицу значений как параметр запроса
Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ);
// Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
// Дальнейшая работа с ВремТаблицаОтгрузок в следующих запросах
ТекстЗапроса2 =
"ВЫБРАТЬ
| ВТ.ДокументОтгрузки,
| ВТ.Сумма
|ИЗ
| ВремТаблицаОтгрузок КАК ВТ";
Запрос2 = Новый Запрос(ТекстЗапроса2);
Запрос2.МенеджерВременныхТаблиц = Запрос.МенеджерВременныхТаблиц; // Используем тот же менеджер
Выборка = Запрос2.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить("Документ: " + Выборка.ДокументОтгрузки + ", Сумма: " + Выборка.Сумма);
КонецЦикла;
В этом примере мы видим, как ТаблицаДанных с колонкой ДокументОтгрузки (составной тип) успешно передается в запрос и используется во временной таблице ВремТаблицаОтгрузок. Ключевой момент — это корректное описание типа колонки на этапе создания таблицы значений.
Даже если мы правильно описали составной тип колонки в таблице значений, в некоторых сценариях при работе с запросами нам может понадобиться более детальный контроль над тем, как платформа обрабатывает поля составных типов. Это особенно актуально, когда мы хотим:
Для таких задач в 1С используется функция ВЫРАЗИТЬ(). Синтаксис ВЫРАЗИТЬ(<Выражение> КАК <Тип>) позволяет указать конкретный тип, который нас интересует. Если значение не соответствует указанному типу, ВЫРАЗИТЬ() вернет NULL. При возникновении ошибок несовместимости типов в операциях сравнения будет полезно изучить рекомендации по решению проблем со сравнением строк и ссылок в запросе.
Рассмотрим, как ВЫРАЗИТЬ() помогает в различных сценариях:
1. Явный выбор конкретного типа из составного поля:
Если мы хотим в запросе получить данные, явно разделенные по типам документов, мы можем использовать ВЫРАЗИТЬ():
ВЫБРАТЬ
ВЫРАЗИТЬ(ВТ.ДокументОтгрузки КАК ДокументСсылка.РеализацияТоваровУслуг) КАК РеализацияТоваровУслуг,
ВЫРАЗИТЬ(ВТ.ДокументОтгрузки КАК ДокументСсылка.ВозвратТоваровОтПокупателя) КАК ВозвратТоваровОтПокупателя,
ВТ.Сумма
ИЗ
ВремТаблицаОтгрузок КАК ВТ
В этом примере, если ДокументОтгрузки является РеализациейТоваровУслуг, то поле РеализацияТоваровУслуг будет содержать ссылку на документ, а ВозвратТоваровОтПокупателя будет NULL, и наоборот. Это позволяет нам работать с каждым типом отдельно в одном запросе.
2. Фильтрация и разыменование "через точку" по определенному типу:
Когда нам нужно отфильтровать данные или получить свойства конкретного типа документа из поля составного типа, прямое обращение без ВЫРАЗИТЬ() может привести к ошибкам или неоптимальному запросу (платформа может пытаться присоединиться ко всем возможным таблицам). Используя ВЫРАЗИТЬ(), мы явно указываем, какой тип нас интересует:
// Пример использования ВЫРАЗИТЬ() для фильтрации по номеру Реализации
ВЫБРАТЬ
ВТ.ДокументОтгрузки,
ВТ.Сумма
ИЗ
ВремТаблицаОтгрузок КАК ВТ
ГДЕ
ВЫРАЗИТЬ(ВТ.ДокументОтгрузки КАК ДокументСсылка.РеализацияТоваровУслуг).Номер = "РТ000000001"
ИЛИ ВЫРАЗИТЬ(ВТ.ДокументОтгрузки КАК ДокументСсылка.ВозвратТоваровОтПокупателя).Дата >= &ДатаНачала
Здесь мы безопасно обращаемся к свойствам Номер и Дата, при этом фильтруя только те записи, где тип документа соответствует указанному. Если ДокументОтгрузки не является РеализациейТоваровУслуг, то ВЫРАЗИТЬ(...) вернет NULL, и сравнение NULL.Номер = "..." будет некорректным, что предотвратит ошибку.
3. Использование ВЫБОР КОГДА ССЫЛКА в комбинации с ВЫРАЗИТЬ():
Для более гибкой логики, особенно когда нужно проверить тип ссылки перед разыменованием, мы можем использовать конструкцию ВЫБОР КОГДА ... ССЫЛКА ... ТОГДА ВЫРАЗИТЬ(...). Это позволяет избежать ошибок при попытке разыменования NULL или неподходящего типа:
ВЫБРАТЬ
ВТ.ДокументОтгрузки,
ВТ.Сумма,
ВЫБОР
КОГДА ВТ.ДокументОтгрузки ССЫЛКА Документ.РеализацияТоваровУслуг
ТОГДА ВЫРАЗИТЬ(ВТ.ДокументОтгрузки, Документ.РеализацияТоваровУслуг).Проведен
КОГДА ВТ.ДокументОтгрузки ССЫЛКА Документ.ВозвратТоваровОтПокупателя
ТОГДА ВЫРАЗИТЬ(ВТ.ДокументОтгрузки, Документ.ВозвратТоваровОтПокупателя).Основание
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК ДополнительноеПоле
ИЗ
ВремТаблицаОтгрузок КАК ВТ
Этот подход гарантирует, что мы пытаемся получить свойство Проведен только для Реализаций, а Основание — только для Возвратов, обеспечивая надежность и корректность запроса.
Ошибка "Тип не может быть выбран в запросе" при работе с составными типами в ТаблицахЗначений, передаваемых в запросы 1С, чаще всего вызвана некорректным описанием типов колонок. Мы выяснили, что необходимо либо передавать массив объектов Тип(), либо строку с именами типов через запятую в конструктор Новый ОписаниеТипов(). После того как колонки таблицы значений описаны правильно, ее можно без проблем использовать в запросах.
Мы также разобрали, что функция ВЫРАЗИТЬ() является мощным инструментом для управления составными типами непосредственно в тексте запроса. Она позволяет явно приводить значения к конкретному типу, что не только предотвращает ошибки при разыменовании или фильтрации, но и может значительно оптимизировать производительность запросов. Используя эти подходы, мы сможем эффективно и надежно работать с динамическими данными, содержащими составные типы, в своих разработках на платформе 1С:Предприятие.