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