Почему запрос к регистру сведений возвращает пустую выборку в коде, хотя в консоли все работает?

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

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

Анализ исходной ситуации

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


Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Сотрудники.Подразделение.Наименование КАК ПодразделениеОрганизации,
|    Сотрудники.Должность.Наименование КАК Должность
|ИЗ
|    РегистрСведений.Сотрудники.СрезПоследних(&ДатаДокумента, Сотрудник = &Сотрудник) КАК Сотрудники";

Запрос.УстановитьПараметр("Сотрудник",    Сотрудник);
Запрос.УстановитьПараметр("ДатаДокумента",    СсылкаНаОбъект.Дата);

Результат = Запрос.Выполнить();
ВыборкаДетЗап = Результат.Выбрать();
Пока ВыборкаДетЗап.Следующий() Цикл
    Подразделение = ВыборкаДетЗап.ПодразделениеОрганизации;
    Должность = ВыборкаДетЗап.Должность;
КонецЦикла;

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

Выявление причины: Объект против Ссылки

Главная причина кроется в типе значения, которое передается в параметр &Сотрудник. В платформе 1С существует принципиальное различие между ссылкой на элемент базы данных и самим объектом, находящимся в оперативной памяти:

Когда мы передаем переменную, содержащую тип СправочникОбъект, напрямую в параметр запроса 1С, платформа транслирует этот параметр в СУБД. База данных пытается сравнить сложную структуру объекта с простым GUID в поле регистра сведений. Такое сравнение всегда возвращает ложь, из-за чего выборка оказывается пустой.

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

Решение проблемы: Получение ссылки из объекта

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


Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Сотрудники.Подразделение.Наименование КАК ПодразделениеОрганизации,
|    Сотрудники.Должность.Наименование КАК Должность
|ИЗ
|    РегистрСведений.Сотрудники.СрезПоследних(&ДатаДокумента, Сотрудник = &Сотрудник) КАК Сотрудники";

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

Результат = Запрос.Выполнить();
ВыборкаДетЗап = Результат.Выбрать();
Пока ВыборкаДетЗап.Следующий() Цикл
    Подразделение = ВыборкаДетЗап.ПодразделениеОрганизации;
    Должность = ВыборкаДетЗап.Должность;
КонецЦикла;

Если изначально переменная Сотрудник уже была ссылкой, обращение к свойству Ссылка не вызовет ошибки — платформа просто вернет то же самое значение. Поэтому использование конструкции Сотрудник.Ссылка является наиболее безопасным и универсальным решением.

Методика быстрой диагностики в отладчике

В табло отладчика 1С и объект, и ссылка отображаются абсолютно одинаково — в виде своего строкового представления (например, "Иванов Иван Иванович"). Из-за этого визуально обнаружить ошибку крайне сложно. Проанализируем два надежных способа быстрой диагностики типа переменной:

  1. Проверка через функцию ТипЗнч. Откроем окно «Вычислить выражение» (сочетание клавиш Shift + F9) и введем выражение: ТипЗнч(Сотрудник). Если результатом вычисления будет тип СправочникОбъект.Сотрудники, то перед нами объект. Правильный тип для передачи в запрос — СправочникСсылка.Сотрудники.
  2. Проверка структуры данных формы. При работе на управляемых формах переменные могут иметь тип ДанныеФормыСтруктура или ДанныеФормыЭлементДерева. Они также визуально схожи со ссылками, но не могут быть переданы в запрос напрямую. Проверка свойства Сотрудник.Ссылка в табло отладчика сразу покажет реальный тип ссылки в базе данных.

Другие причины отсутствия записей при срезе последних

Если мы убедились, что в параметры запроса передается именно ссылка, но выборка все равно пуста, рассмотрим другие распространенные архитектурные ошибки:

  1. Путаница в похожих справочниках.

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

  2. Проблема границы времени (секунда в секунду).

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

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

    
    ГраницаСреза = Новый Граница(СсылкаНаОбъект.МоментВремени(), ВидГраницы.Включая);
    Запрос.УстановитьПараметр("ДатаДокумента", ГраницаСреза);
    
  3. Пустая ссылка в качестве параметра.

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

Важное правило производительности при работе со Срезом последних

Обратим внимание на то, как именно накладывается условие по сотруднику в нашем примере. Отбор передан в качестве параметра виртуальной таблицы:

РегистрСведений.Сотрудники.СрезПоследних(&ДатаДокумента, Сотрудник = &Сотрудник)

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


// ТАК ДЕЛАТЬ НЕЛЬЗЯ!
Запрос.Текст =
"ВЫБРАТЬ
|    Сотрудники.Подразделение.Наименование КАК ПодразделениеОрганизации,
|    Сотрудники.Должность.Наименование КАК Должность
|ИЗ
|    РегистрСведений.Сотрудники.СрезПоследних(&ДатаДокумента) КАК Сотрудники
|ГДЕ
|    Сотрудники.Сотрудник = &Сотрудник";

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

Передавая отборы внутрь скобок параметров виртуальной таблицы, мы заставляем СУБД строить срез только для одной конкретной записи, что выполняется платформой мгновенно.

← На главную