Как в запросе 1С определить тип параметра и обратиться к общему реквизиту для разных типов документов?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов или алгоритмов обработки данных мы часто сталкиваемся с ситуацией, когда в запрос передается параметр (ссылка), который может принадлежать одному из нескольких типов документов. Например, это может быть ссылка на Документ.ПриемНаРаботу или Документ.Увольнение.

Возникает задача: нам нужно получить значение реквизита, который есть в обоих документах (например, «ФизическоеЛицо» или «Контрагент»), используя этот параметр. Однако, если попытаться в лоб использовать конструкцию ВЫРАЗИТЬ или проверять тип внутри запроса, платформа часто выдает ошибку несовместимости типов или ошибку SDBL.

В этой статье мы подробно разберем, почему возникает эта ошибка, и рассмотрим три способа решения задачи: от простого метода, предложенного на форуме, до архитектурно правильных подходов.

Почему возникает ошибка при использовании ВЫРАЗИТЬ?

Давайте проанализируем ситуацию. У нас есть параметр &Ссылка. Мы пытаемся написать такой код:


ВЫБРАТЬ
    ВЫБОР
        КОГДА ТИПЗНАЧЕНИЯ(&Ссылка) = ТИП(Документ.Счет) ТОГДА
            ВЫРАЗИТЬ(&Ссылка КАК Документ.Счет).Контрагент
        ИНАЧЕ
            ВЫРАЗИТЬ(&Ссылка КАК Документ.Накладная).Контрагент
    КОНЕЦ КАК Контрагент

Казалось бы, логика верная. Однако платформа 1С транслирует этот запрос в SQL. Особенность транслятора заключается в том, что он пытается построить план выполнения и проверить типизацию до реального выполнения условий. Даже если условие КОГДА ложно, механизм проверки типов видит, что вы пытаетесь применить типизацию (CAST) к параметру, который может быть другого типа. Это часто приводит к ошибке «Несовместимые типы выражений» или ошибкам на уровне СУБД.

Способ 1. Использование ОБЪЕДИНИТЬ ВСЕ (Решение с форума)

Самый простой способ обойти проблему типизации параметра — это отказаться от попыток определить тип внутри одного поля и сделать выборку из конкретных таблиц, объединив результаты. Поскольку мы передаем конкретную ссылку, она найдется только в одной из таблиц.

Рассмотрим, как это реализуется на практике:


ВЫБРАТЬ
    Док.ФизическоеЛицо КАК ФизЛицо
ИЗ
    Документ.ПриемНаРаботу КАК Док
ГДЕ
    Док.Ссылка = &Ссылка

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Док.ФизическоеЛицо КАК ФизЛицо
ИЗ
    Документ.Увольнение КАК Док
ГДЕ
    Док.Ссылка = &Ссылка

Как это работает:

  1. Запрос пытается найти документ с переданной ссылкой в таблице ПриемНаРаботу.
  2. Затем он пытается найти этот же документ в таблице Увольнение.
  3. Результаты объединяются. Так как уникальный идентификатор (GUID) ссылки уникален в пределах базы (обычно), запись будет найдена только в одной из таблиц (или ни в одной, если ссылка битая), а вторая выборка вернет пустоту.

Этот метод хорош своей простотой и наглядностью. Он работает безотказно и не вызывает ошибок компиляции запроса.

Способ 2. Динамическое формирование текста запроса (Оптимальный по производительности)

Хотя первый способ рабочий, он не является идеальным с точки зрения производительности СУБД. При использовании ОБЪЕДИНИТЬ ВСЕ серверу баз данных приходится обращаться к индексам двух (или более) таблиц. Если типов документов будет 10, сервер будет сканировать 10 таблиц, хотя мы заранее знаем, что документ может быть только в одной.

Более грамотный подход — определить тип значения еще на этапе встроенного языка 1С и сформировать текст запроса именно для нужной таблицы.

Разберем алгоритм:


// 1. Создаем заготовку запроса
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Док", СсылкаНаДокумент);

// 2. Определяем имя таблицы в зависимости от типа ссылки
ИмяТаблицы = "";
Если ТипЗнч(СсылкаНаДокумент) = Тип("ДокументСсылка.ПриемНаРаботу") Тогда
    ИмяТаблицы = "Документ.ПриемНаРаботу";
ИначеЕсли ТипЗнч(СсылкаНаДокумент) = Тип("ДокументСсылка.Увольнение") Тогда
    ИмяТаблицы = "Документ.Увольнение";
КонецЕсли;

// 3. Формируем текст запроса динамически
Если ЗначениеЗаполнено(ИмяТаблицы) Тогда
    Запрос.Текст = СтрШаблон("
    |ВЫБРАТЬ
    |   Док.ФизическоеЛицо КАК ФизЛицо
    |ИЗ
    |   %1 КАК Док
    |ГДЕ
    |   Док.Ссылка = &Док", ИмяТаблицы);
    
    Результат = Запрос.Выполнить();
КонецЕсли;

Преимущества этого метода:

Способ 3. Универсальный подход через Метаданные или БСП

Если вы хотите сделать код красивым и не плодить условия Если...ИначеЕсли, можно воспользоваться механизмами платформы или БСП (Библиотеки Стандартных Подсистем).

Вариант с БСП

Если в вашей конфигурации внедрена БСП, используйте функцию общего модуля, которая внутри себя реализует «Способ 2», но в универсальном виде:


// Получаем значение реквизита одной строкой кода
ФизЛицо = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(СсылкаНаДокумент, "ФизическоеЛицо");

Эта функция сама определит имя таблицы метаданных, построит быстрый запрос и вернет значение. Это наиболее рекомендуемый способ для типовых конфигураций (ERP, КА, ЗУП, Бухгалтерия).

Вариант с Метаданными (самостоятельная реализация)

Если БСП нет, можно написать универсальный генератор запроса:


ИмяТаблицы = СсылкаНаДокумент.Метаданные().ПолноеИмя(); 
// Например, вернет "Документ.ПриемНаРаботу"

Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Док.ФизическоеЛицо ИЗ " + ИмяТаблицы + " КАК Док ГДЕ Док.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);

Перед этим желательно проверить, есть ли вообще такой реквизит у данного типа документа:


Если СсылкаНаДокумент.Метаданные().Реквизиты.Найти("ФизическоеЛицо") <> Неопределено Тогда
    // Выполняем запрос
КонецЕсли;

Чего делать не стоит

В обсуждениях часто предлагают просто обращаться к реквизиту через точку, например: ДокументСсылка.ФизическоеЛицо.

Давайте выясним, почему это может быть плохой идеей:

Итог

Для решения задачи типизации параметра запроса мы рекомендуем:

  1. Использовать ОбщегоНазначения.ЗначениеРеквизитаОбъекта, если вы работаете в современной типовой конфигурации.
  2. Использовать Динамическое формирование текста запроса (Способ 2), если вы пишете свой механизм и вам важна производительность.
  3. Использовать ОБЪЕДИНИТЬ ВСЕ (Способ 1), если вам нужно быстрое решение «здесь и сейчас» для небольшого количества типов документов и производительность не является критическим узким местом.
← На главную