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