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