Почему запрос в 1С возвращает разные результаты в коде и в консоли запросов?

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

Каждый разработчик на платформе 1С:Предприятие рано или поздно сталкивается с мистической на первый взгляд ситуацией: один и тот же запрос, запущенный в консоли запросов, прекрасно работает и возвращает нужные данные, но при выполнении в программном коде конфигурации выдает пустой результат. Более того, при попытке проанализировать выборку в отладчике мы видим надпись «Ошибка чтения значения» для всех доступных полей.

В этой статье мы подробно разберем, почему возникает такое расхождение, как правильно интерпретировать поведение отладчика и какие скрытые механизмы платформы 1С и СУБД влияют на результат выполнения пакетных запросов.

Причина 1. Специфика работы отладчика и курсора выборки

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


Выборка = РезультатЗапроса.Выбрать();
// Точка останова установлена на этой строке
Пока Выборка.Следующий() Цикл
    СтрокаСоставКомиссии = СоставКомиссии.Добавить();
    ЗаполнитьЗначенияСвойств(СтрокаСоставКомиссии, Выборка);
КонецЦикла;

Если мы установим точку останова на строке Пока Выборка.Следующий() Цикл и попытаемся посмотреть в табло отладчика значения полей, например, Выборка.ФизЛицо или Выборка.Должность, мы гарантированно получим сообщение «Ошибка чтения значения». Почему это происходит?

Дело в том, что метод Выбрать() инициализирует объект выборки, но устанавливает его курсор в позицию перед первой записью. В этой позиции данные еще не считаны из базы данных в текущий буфер объекта. Доступ к полям выборки закрыт, так как физически указатель никуда не ссылается.

Правильный способ отладки выборки:

  1. Перенесите точку останова внутрь цикла — на строку СтрокаСоставКомиссии = СоставКомиссии.Добавить();.
  2. Только после того, как шаг Выборка.Следующий() выполнится и вернет значение Истина, курсор переместится на первую строчку данных. В этот момент все свойства выборки станут доступны для анализа в табло.
  3. Если вам необходимо сразу оценить, вернул ли запрос хоть какие-то данные, не заходя в цикл, используйте метод выгрузки в таблицу значений: в табло отладки вычислите выражение РезультатЗапроса.Выгрузить(). Это позволит моментально увидеть количество строк и структуру колонок.

Причина 2. Недетерминированная сортировка при использовании конструкции «ПЕРВЫЕ»

Проанализируем первый пакет запроса, который выбирает последний проведенный документ по организации:


ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
    ТребованиеНакладная.Ссылка КАК Ссылка
ПОМЕСТИТЬ ПоследнийДокумент
ИЗ
    Документ.ТребованиеНакладная КАК ТребованиеНакладная
ГДЕ
    ТребованиеНакладная.Организация = &Организация
    И ТребованиеНакладная.Проведен
УПОРЯДОЧИТЬ ПО
    ТребованиеНакладная.Дата УБЫВ

В этом пакете кроется серьезная логическая уязвимость. Сортировка производится только по одному полю — Дата УБЫВ. В реальной базе данных очень часто создается несколько документов в рамках одной и той же секунды (например, при групповом перепроведении документов или массовой загрузке из внешних систем).

Если в системе присутствуют несколько документов с абсолютно идентичной датой и временем, СУБД (будь то MS SQL Server, PostgreSQL или IBM DB2) при выполнении инструкции ПЕРВЫЕ 1 может вернуть любой из этих документов. Сортировка в данном случае является недетерминированной (непредсказуемой). Порядок выдачи строк зависит от физического расположения записей на диске, используемых индексов и текущего плана запроса, который строит оптимизатор СУБД.

В результате мы получаем следующую картину:

Решение проблемы: всегда делайте сортировку уникальной при использовании ограничений количества строк. Добавьте в секцию УПОРЯДОЧИТЬ ПО уникальный идентификатор документа (ссылку):


УПОРЯДОЧИТЬ ПО
    ТребованиеНакладная.Дата УБЫВ,
    ТребованиеНакладная.Ссылка УБЫВ

Это гарантирует, что и в консоли, и в программном коде система выберет один и тот же документ.

Причина 3. Ограничение доступа на уровне записей (RLS)

Еще одна частая причина расхождения результатов — работа механизма RLS (Row-Level Security). Обратите внимание на ключевое слово РАЗРЕШЕННЫЕ в тексте запроса.

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

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

Кроме того, помните о специфике пакетных запросов с RLS в 1С. Если вы используете ВЫБРАТЬ РАЗРЕШЕННЫЕ в первом запросе пакета для создания временной таблицы, крайне рекомендуется указывать ключевое слово РАЗРЕШЕННЫЕ и во всех последующих запросах пакета, где происходит обращение к физическим таблицам (в нашем случае — к табличной части документа). Отсутствие этого ключевого слова в последующих пакетах при включенном RLS может приводить к непредсказуемому поведению платформы.

Причина 4. Различия в параметрах и контексте выполнения

Проанализируем установку параметров запроса. В коде конфигурации мы видим:

Запрос.УстановитьПараметр("Организация", Организация);

При отладке обязательно проверьте, что именно содержится в переменной Организация в момент передачи в запрос. Возможны следующие ситуации:

Резюме и правильный шаблон запроса

Давайте объединим все исправления в единый надежный шаблон запроса, который исключит недетерминированную выборку и решит проблему пустых результатов:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
|    ТребованиеНакладная.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ПоследнийДокумент
|ИЗ
|    Документ.ТребованиеНакладная КАК ТребованиеНакладная
|ГДЕ
|    ТребованиеНакладная.Организация = &Организация
|    И ТребованиеНакладная.Проведен
|
|УПОРЯДОЧИТЬ ПО
|    ТребованиеНакладная.Дата УБЫВ,
|    ТребованиеНакладная.Ссылка УБЫВ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ РАЗРЕШЕННЫЕ
|    ТребованиеНакладнаяСоставКомиссии.НомерСтроки КАК НомерСтроки,
|    ТребованиеНакладнаяСоставКомиссии.ФизЛицо КАК ФизЛицо,
|    ТребованиеНакладнаяСоставКомиссии.Должность КАК Должность
|ИЗ
|    ПоследнийДокумент КАК ПоследнийДокумент
|        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ТребованиеНакладная.СоставКомиссии КАК ТребованиеНакладнаяСоставКомиссии
|        ПО ПоследнийДокумент.Ссылка = ТребованиеНакладнаяСоставКомиссии.Ссылка
|
|УПОРЯДОЧИТЬ ПО
|    НомерСтроки";

Запрос.УстановитьПараметр("Организация", Организация);
РезультатЗапроса = Запрос.Выполниться();

Применяя данные правила упорядочивания данных, учитывая контекст прав доступа текущего пользователя (RLS) и правильно используя точки останова в цикле выборки, вы навсегда избавитесь от проблемы расхождения результатов работы запросов в коде и консоли.

← На главную