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