Каждый разработчик на платформе «1С:Предприятие» рано или поздно сталкивается с необходимостью передать в запрос внешние данные. Чаще всего в качестве такого источника выступает обычная ТаблицаЗначений или ТабличнаяЧасть документа, передаваемая через параметры запроса. Однако при попытке выполнить с этими данными сложные операции (например, объединение или соединение) платформа возвращает крайне неприятную ошибку: «Содержимое объекта данных может быть выбрано только во временную таблицу».
В этой статье мы подробно разберем по шагам, почему возникает эта ошибка с точки зрения взаимодействия 1С и СУБД, проанализируем неработающий код и покажем, как правильно спроектировать пакетный запрос для обхода этого ограничения.
Чтобы понять природу этого поведения, рассмотрим подробнее, как работает платформа «1С:Предприятие» на физическом уровне. Платформа построена по трехзвенной архитектуре: клиент, сервер приложений 1С и сервер баз данных (СУБД — MS SQL Server, PostgreSQL, IBM DB2 или Oracle).
Когда мы создаем объект типа ТаблицаЗначений в коде, эти данные хранятся исключительно в оперативной памяти сервера приложений 1С. Сама СУБД ничего не знает о существовании этой таблицы. Для того чтобы СУБД могла выполнить по ней запрос (например, соединить с физической таблицей справочника или документа в базе данных), эти данные необходимо физически перенести на сторону сервера СУБД — то есть создать реальную временную таблицу в системной базе данных (например, в tempdb для MS SQL Server) и заполнить её строками.
Транслятор запросов 1С умеет выполнять эту операцию переноса данных из оперативной памяти в базу данных только с помощью максимально простой, изолированной инструкции:
ВЫБРАТЬ Поля ПОМЕСТИТЬ ВременнаяТаблица ИЗ &ВнешнийИсточник
СУБД не способна «на лету» выполнять объединения (UNION), соединения (JOIN) или группировки над данными, которых еще физически нет в базе данных. Именно поэтому платформа накладывает жесткое ограничение: любой внешний источник при первом обращении к нему в запросе должен быть безоговорочно и изолированно помещен во временную таблицу. Любые другие операции в этом же пакете запроса запрещены.
Посмотрим на пример, с которым столкнулся автор темы на форуме. Он попытался сразу объединить данные двух параметров (&Товары и &Работы) и поместить результат в одну временную таблицу:
Запрос = Новый Запрос("
|ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| Товары.СуммаСНДС КАК СуммаСНДС,
| Товары.Отменено КАК Отменено
|ПОМЕСТИТЬ
| Товары
|ИЗ
| &Товары КАК Товары
|Объединить все
|ВЫБРАТЬ
| Работы.Номенклатура ,
| Работы.СуммаСНДС ,
| Работы.Отменено
|ИЗ
| &Работы КАК Работы
|;
|... дальнейший текст запроса ...
|")
Этот запрос гарантированно завершится аварийно. Платформа анализирует первый пакет запроса и видит, что разработчик пытается выполнить операцию ОБЪЕДИНИТЬ ВСЕ непосредственно для двух внешних источников &Товары и &Работы еще до того, как они были записаны в базу данных. Транслятор запросов выдает ошибку, так как правила работы с внешними объектами были грубо нарушены.
При передаче таблиц значений или табличных частей в качестве параметров запроса необходимо строго соблюдать следующие правила:
ИЗ &ТаблицаПараметр), категорически запрещено использовать:
ОБЪЕДИНИТЬ и ОБЪЕДИНИТЬ ВСЕ).ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ и т. д.).СГРУППИРОВАТЬ ПО) и агрегатные функции (СУММА, КОЛИЧЕСТВО, МАКСИМУМ).Товары.Номенклатура.ТипНоменклатуры при первой выборке из внешнего источника. Обращение через точку платформа неявно преобразует в левое соединение с соответствующей таблицей справочника БД. Соединения на данном этапе запрещены. Сначала нужно поместить ссылку во временную таблицу, а на следующем шаге уже обращаться к ее реквизитам.ТаблицаЗначений программно в коде, обязательно задавайте жесткие типы колонок через объект ОписаниеТипов. В противном случае СУБД не сможет определить типы полей для создания временной таблицы, и вы получите ошибку: «Тип не может быть выбран в запросе».Для обхода ограничений нам необходимо применить классический шаблон проектирования пакетных запросов в 1С. Мы должны разделить задачу на три логических этапа:
Посмотрим на пример правильной реализации запроса, который полностью решает проблему автора:
// Создаем менеджер временных таблиц, чтобы связать пакеты запроса
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Номенклатура КАК Номенклатура,
| Товары.СуммаСНДС КАК СуммаСНДС,
| Товары.Отменено КАК Отменено
|ПОМЕСТИТЬ ВТ_ТоварыПараметр
|ИЗ
| &Товары КАК Товары
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Работы.Номенклатура КАК Номенклатура,
| Работы.СуммаСНДС КАК СуммаСНДС,
| Работы.Отменено КАК Отменено
|ПОМЕСТИТЬ ВТ_РаботыПараметр
|ИЗ
| &Работы КАК Работы
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ТоварыПараметр.Номенклатура КАК Номенклатура,
| ВТ_ТоварыПараметр.СуммаСНДС КАК СуммаСНДС,
| ВТ_ТоварыПараметр.Отменено КАК Отменено
|ПОМЕСТИТЬ ВТ_ВсеОбъекты
|ИЗ
| ВТ_ТоварыПараметр КАК ВТ_ТоварыПараметр
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| ВТ_РаботыПараметр.Номенклатура,
| ВТ_РаботыПараметр.СуммаСНДС,
| ВТ_РаботыПараметр.Отменено
|ИЗ
| ВТ_РаботыПараметр КАК ВТ_РаботыПараметр
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ЕСТЬNULL(СУММА(ВТ_ВсеОбъекты.СуммаСНДС), 0) КАК СуммаСНДС
|ИЗ
| ВТ_ВсеОбъекты КАК ВТ_ВсеОбъекты
|ГДЕ
| НЕ ВТ_ВсеОбъекты.Отменено
| И (((ВТ_ВсеОбъекты.Номенклатура.ТипНоменклатуры <> ЗНАЧЕНИЕ(Перечисление.ТипыНоменклатуры.МногооборотнаяТара)
| ИЛИ (НЕ &ВернутьМногооборотнуюТару) ИЛИ &ТребуетсяЗалогЗаТару)
| И НЕ &ТолькоЗалогЗаТару)
| ИЛИ (ВТ_ВсеОбъекты.Номенклатура.ТипНоменклатуры = ЗНАЧЕНИЕ(Перечисление.ТипыНоменклатуры.МногооборотнаяТара)
| И &ВернутьМногооборотнуюТару
| И &ТребуетсяЗалогЗаТару
| И &ТолькоЗалогЗаТару))";
// Установка параметров и выполнение запроса
Запрос.УстановитьПараметр("Товары", ТаблицаТоваров);
Запрос.УстановитьПараметр("Работы", ТаблицаРабот);
// Установите остальные параметры запроса...
РезультатЗапроса = Запрос.Выполнить();
Что изменилось в этом решении? Мы создали две независимые временные таблицы: ВТ_ТоварыПараметр и ВТ_РаботыПараметр. СУБД успешно создала их физические копии в базе данных. Третьим пакетом мы спокойно выполнили операцию ОБЪЕДИНИТЬ ВСЕ над уже существующими временными таблицами и сохранили общий результат в ВТ_ВсеОбъекты. И, наконец, в четвертом пакете мы без проблем обратились к реквизиту через точку (ВТ_ВсеОбъекты.Номенклатура.ТипНоменклатуры) и выполнили агрегатную функцию СУММА с группировкой.
Использование этого шаблона проектирования гарантирует высокую производительность запросов, легкую отладку в консоли запросов и полное отсутствие подобных ошибок СУБД в ваших разработках.