Почему запрос к срезу последних возвращает пустой результат и как исправить ошибку передачи данных XDTO в 1С?

Программист 1С v8.3 (Управляемые формы) Промышленность, строительство и АПК
← На главную

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

Разберем причину пустого результата запроса

Представим, что мы написали следующий код для получения цены и валюты:


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

Выборка = Запрос.Выполнить.Выбрать; 
Выборка.Следующий; 
Объект.ЦенаПродукта = Выборка.Цена; 

Сначала обратим внимание на синтаксические ошибки. В языке 1С вызов методов обязательно требует круглых скобок. Конструкция Запрос.Выполнить.Выбрать вызовет ошибку. Правильное написание выглядит так:


Выборка = Запрос.Выполнить().Выбрать(); 
Выборка.Следующий();

Выясним причину, по которой запрос не находит данные:

  1. Несовпадение параметров: В консоли запросов разработчик вручную выбирает конкретный элемент справочника. В коде передается реквизит Объект.Виноград. Если пользователь на форме еще не выбрал материал (поле пустое), запрос ничего не найдет. Перед выполнением запроса всегда проверяйте, заполнены ли ключевые параметры.
  2. Проблема с датой и временем: Если документ новый и не записан, его время может быть равно началу дня (00:00:00). Если цены в регистре установлены позже в этот же день (например, в 12:00:00), срез последних на 00:00:00 их просто не увидит.
  3. Использование момента времени: Если документ уже записан, для получения цен, актуальных на момент его проведения, следует использовать конструкцию Новый Граница(Объект.МоментВремени(), ВидГраницы.Включая). Если же документ создается и еще не сохранен в базу, лучше использовать ТекущаяДатаСеанса().

Проанализируем ошибку передачи данных XDTO между клиентом и сервером

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


&НаСервере 
Функция ПолучитьЦенуВинограта()
    // ... выполнение запроса ...
    Выборка = Запрос.Выполнить().Выбрать(); 
    Возврат Выборка; 
КонецФункции 

При вызове такой функции возникает критическая ошибка: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. Ошибка отображения типов: Отсутствует отображение для типа 'ВыборкаИзРезультатаЗапроса'.

Разберем подробнее, почему это происходит. В управляемом приложении 1С четко разделен контекст: есть Клиент (пользовательский интерфейс) и Сервер (работа с базой данных). Объекты, напрямую связанные с СУБД, такие как Запрос, РезультатЗапроса и ВыборкаИзРезультатаЗапроса, существуют исключительно на сервере. Платформа физически не умеет передавать (сериализовать) их по сети на клиент, так как для них нет соответствующего описания в формате XDTO.

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


&НаСервере
Функция ПолучитьЦенуИВалютуСервер(Материал, Организация, ДатаДокумента)

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

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

Посмотрим на оптимизацию текста запроса

В первоначальном коде автор использует оператор ПЕРВЫЕ 1. При работе с виртуальной таблицей СрезПоследних это избыточно. Логика самого регистра сведений устроена так, что для уникальной комбинации измерений (Организация, ТипЦен, Материал) на конкретную дату система и так вернет только одну, наиболее актуальную запись. Конструкция ПЕРВЫЕ 1 здесь не несет пользы.

Зато автор абсолютно правильно разместил условия (отборы) внутри параметров виртуальной таблицы: Организация = &Организация И Материал = &Материал. Разберем по шагам, почему это важно: если бы эти условия были написаны в секции ГДЕ, платформа сначала получила бы срез последних цен абсолютно по всем материалам и организациям в базе, и только потом отфильтровала бы нужный виноград. Помещение отборов внутрь параметров виртуальной таблицы многократно ускоряет выполнение запроса и является правилом хорошего тона при разработке на 1С.

← На главную