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