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