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