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