Почему запрос в отладчике 1С возвращает Неопределено, хотя в консоли всё работает?

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

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

Анализ исходного кода и проявление проблемы

Рассмотрим типовую процедуру выборки номенклатуры и цен, которая вызвала затруднение:


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

При проверке в отладчике на последней строке разработчик видит, что в переменной ТЗ находится значение Неопределено. Из-за этого делается ложный вывод, что метод Выполнить().Выгрузить() не сработал. Выясним реальные технические причины такого поведения платформы.

Причина 1: Особенности точки останова и область видимости переменных

Метод Выгрузить() объекта РезультатЗапроса технически никогда не возвращает Неопределено. Если запрос выполнен без исключений, возвращается либо заполненная, либо пустая таблица значений (объект типа ТаблицаЗначений с количеством строк 0). Значение Неопределено появляется исключительно из-за некорректного момента замера в отладчике:

  1. Остановка до вычисления строки: если точка останова установлена на строку ТЗ = Запрос.Выполнить().Выгрузить();, отладчик прерывает выполнение до выполнения оператора присваивания. В этот момент переменная ТЗ еще не инициализирована и содержит исходное значение Неопределено.
  2. Выход за пределы процедуры: если на последней исполняемой строке нажать клавишу перехода к следующему шагу (F10), процедура немедленно завершает свою работу. Контекст выполнения уничтожается, стек очищается, а локальная переменная ТЗ удаляется из оперативной памяти сборщиком мусора платформы 1С. В окне «Табло» или подсказке отладчика ее значение моментально превращается в Неопределено.

Чтобы корректно увидеть результат работы запроса в отладчике, применим следующий прием:


РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
    ТЗ = РезультатЗапроса.Выгрузить();
КонецЕсли;
// Временная точка останова для анализа:
Сообщить("Выборка завершена, строк: " + ТЗ.Количество());

Остановившись на строке с методом Сообщить(), мы гарантированно остаемся в контексте процедуры и можем спокойно открыть значение переменной ТЗ в окне «Вычислить выражение» (Shift+F9).

Причина 2: Избыточное разыменование ссылок в условии соединения

Обратим внимание на условие связи таблиц в запросе:

ПО Номенклатура1.Ссылка = ЦеныНоменклатурыСрезПоследних.Номенклатура.Ссылка

Измерение Номенклатура регистра сведений ЦеныНоменклатуры уже само по себе имеет ссылочный тип СправочникСсылка.Номенклатура. Конструкция .Ссылка после имени поля приводит к скрытому неявному соединению таблицы регистра со справочником на уровне СУБД. Это создает дополнительную нагрузку на сервер и усложняет план выполнения запроса. Исправим условие на прямое равенство:

ПО Номенклатура1.Ссылка = ЦеныНоменклатурыСрезПоследних.Номенклатура

Причина 3: Неоптимальный срез последних без отбора по номенклатуре

В исходном запросе условие иерархии Номенклатура1.Ссылка В ИЕРАРХИИ(&Ссылка) находится в секции ГДЕ. Это означает, что виртуальная таблица СрезПоследних сначала собирает цены по абсолютно всей номенклатуре базы данных, удовлетворяющей типу цен, и только потом левое соединение сопоставляет строки с отобранной группой товаров.

Для повышения производительности обязательно передаем отбор по номенклатуре непосредственно в параметры виртуальной таблицы:


ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
    &Дата, 
    ТипЦен = &ТипЦен И Номенклатура В ИЕРАРХИИ(&Ссылка)
) КАК ЦеныНоменклатурыСрезПоследних

Причина 4: Обработка пустых значений (NULL) и время в параметре даты

Проанализируем еще два важных фактора, влияющих на корректность данных:

  1. Проверка на NULL при левом соединении: если для конкретной позиции номенклатуры цена не была зарегистрирована, поле Цена вернет значение NULL. При дальнейшей обработке это может приводить к ошибкам преобразования типов. Заменим прямое обращение функцией ЕСТЬNULL(ЦеныНоменклатурыСрезПоследних.Цена, 0) КАК Цена.
  2. Граница времени в срезе последних: если в качестве параметра &Дата передается дата без времени (с временем 00:00:00), срез последних вернет данные strictly до начала дня. Движения документов «Установка цен номенклатуры», проведенных текущим днем в рабочее время, в выборку не попадут. Для захвата всех изменений текущего дня следует передавать дату конца дня: КонецДня(ОбщегоНазначения.ПолучитьРабочуюДату()).

Итоговый оптимизированный вариант

Объединим все исправления в единый рабочий модуль. Если полученную таблицу значений необходимо передать во внешний контекст (например, в форму документа), перепишем процедуру в функцию:


Функция ПолучитьНоменклатуруПоГруппе(ГруппаТовара, БазовыйТипЦен, РабочаяДата) Экспорт

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

КонецФункции

Такой подход гарантирует стабильность выполнения запроса, исключает лишние соединения таблиц на уровне СУБД, корректно обрабатывает пустые значения цен и позволяет наглядно отслеживать возвращаемые структуры данных при отладке.

← На главную