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