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