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