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

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

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

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

Главные причины ошибок в исходном запросе

Посмотрим на исходный код и выделим основные ошибки, которые приводят к неработоспособности запроса:

  1. Передача параметра неверного типа: В запросе реквизит Событие имеет тип ПеречислениеСсылка.ВидыСобытийОС. Однако в коде в метод УстановитьПараметр() передается обычная строка (например, "Принятие к учету"). Платформа 1С строго контролирует типы данных, поэтому условие равенства строки и ссылки на перечисление никогда не выполнится.
  2. Обращение к несуществующему реквизиту .Ссылка: В тексте запроса используется конструкция СобытияОС.ИнвНомер.Ссылка. Если реквизит ИнвНомер имеет примитивный тип (например, Число или Строка), у него нет реквизита Ссылка. Такое обращение приводит к тому, что условие отбора возвращает ЛОЖЬ, и запрос возвращает пустую выборку.
  3. Некорректная обработка пакета запросов: В исходном коде написано два подзапроса через точку с запятой. При вызове метода Запрос.Выполнить() платформа возвращает результат только последнего подзапроса в пакете (в данном случае — подзапроса списания). Если для выбранного инвентарного номера списание еще не происходило, итоговый результат будет пустым. Для обработки каждого подзапроса пакета отдельно необходимо использовать Запрос.ВыполнитьПакет().

Решение 1: Использование левого соединения таблиц (классический способ)

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

Рассмотрим по шагам построение такого запроса:

  1. В качестве основной таблицы выбираем регистр СобытияОС с фильтром по событию принятию к учету.
  2. Присоединяем ЛЕВОЕ СОЕДИНЕНИЕ тот же регистр для получения даты списания. Если списания не было, соединение вернет значение NULL.
  3. Оборачиваем дату списания в функцию ЕСТЬNULL(), чтобы корректно обработать отсутствие записи.
  4. Передаем в параметры правильные ссылки на элементы перечисления через Перечисления.ВидыСобытийОС....

Сформируем код обработки и текст запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СобытияПринятие.ДатаСобытия КАК ПринятоКУчету,
    |	ЕСТЬNULL(СобытияСписание.ДатаСобытия, ДАТАВРЕМЯ(1, 1, 1)) КАК СнятоСУчета
    |ИЗ
    |	РегистрСведений.СобытияОС КАК СобытияПринятие
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СобытияОС КАК СобытияСписание
    |		ПО СобытияПринятие.ИнвНомер = СобытияСписание.ИнвНомер
    |			И (СобытияСписание.Событие = &СобытиеСписание)
    |ГДЕ
    |	СобытияПринятие.ИнвНомер = &СобИнвНомер
    |	И СобытияПринятие.Событие = &СобытиеПринятие";

Запрос.УстановитьПараметр("СобИнвНомер", ВыбранноеЗначение);
Запрос.УстановитьПараметр("СобытиеПринятие", Перечисления.ВидыСобытийОС.ПринятиеКУчету);
Запрос.УстановитьПараметр("СобытиеСписание", Перечисления.ВидыСобытийОС.Списание);

Выборка = Запрос.Выполнить().Выбрать();

Если Выборка.Следующий() Тогда
    ДатаПринятияКУчету = Выборка.ПринятоКУчету;
    ДатаСнятияСУчета   = Выборка.СнятоСУчета;
Иначе
    Сообщить("Запись о принятии к учету не найдена!");
КонецЕсли;

Решение 2: Группировка и функция МАКСИМУМ с конструкцией ВЫБОР (оптимальный способ)

Существует еще более эффективный подход, который позволяет избежать соединения таблицы самой с собой. Мы можем прочитать записи регистра за один проход, применив агрегатную функцию МАКСИМУМ и условную конструкцию ВЫБОР КОГДА.

Также, чтобы не передавать параметры перечислений из встроенного языка через УстановитьПараметр(), в тексте запроса можно использовать прямую ссылку на значение перечисления с помощью функции ЗНАЧЕНИЕ().

Посмотрим на пример такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СобытияОС.ИнвНомер КАК ИнвНомер,
    |	МАКСИМУМ(ВЫБОР
    |			КОГДА СобытияОС.Событие = ЗНАЧЕНИЕ(Перечисление.ВидыСобытийОС.ПринятиеКУчету)
    |				ТОГДА СобытияОС.ДатаСобытия
    |		КОНЕЦ) КАК ПринятоКУчету,
    |	МАКСИМУМ(ВЫБОР
    |			КОГДА СобытияОС.Событие = ЗНАЧЕНИЕ(Перечисление.ВидыСобытийОС.Списание)
    |				ТОГДА СобытияОС.ДатаСобытия
    |		КОНЕЦ) КАК СнятоСУчета
    |ИЗ
    |	РегистрСведений.СобытияОС КАК СобытияОС
    |ГДЕ
    |	СобытияОС.ИнвНомер = &СобИнвНомер
    |
    |СГРУППИРОВАТЬ ПО
    |	СобытияОС.ИнвНомер";

Запрос.УстановитьПараметр("СобИнвНомер", ВыбранноеЗначение);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    ДатаПринятияКУчету = Выборка.ПринятоКУчету;
    ДатаСнятияСУчета   = Выборка.СнятоСУчета;
КонецЕсли;

Преимущества второго подхода:

← На главную