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

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

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

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

Причина ошибки: Менеджер справочника (CatalogManager) против Ссылки (CatalogRef)

Проанализируем исходную ошибку, с которой столкнулся автор темы. В своем коде он пытается выполнить следующее присвоение и передать его в запрос:


Номенклатура = Справочники.Номенклатура;
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);

При попытке выполнить запрос, содержащий условие Номенклатура = &Номенклатура, система выдает ошибку совместимости типов данных. Выясним, почему это происходит.

Объект Справочники.Номенклатура возвращает тип данных СправочникМенеджер.Номенклатура. Менеджер справочника — это специальный программный класс на платформе 1С, предназначенный для управления справочником в целом (например, создания новых элементов, поиска по коду или наименованию, получения макетов). Менеджер существует только в контексте выполнения кода 1С и не имеет представления в таблицах базы данных SQL.

В то же время, поля регистров (такие как измерение Номенклатура в регистре накопления ТоварыНаСкладах) хранят тип данных СправочникСсылка.Номенклатура. Это уникальный идентификатор (GUID) конкретной записи в таблице базы данных.

Когда СУБД (например, MS SQL Server или PostgreSQL) пытается сравнить поле ссылки (GUID) с переданным в параметр менеджером справочника, это вызывает конфликт типов данных, так как платформа не может преобразовать программный менеджер класса в конкретный GUID записи.

Правило: В параметры запроса всегда необходимо передавать конкретную ссылку на элемент справочника (СправочникСсылка), а не менеджер справочника (СправочникМенеджер).

Для передачи конкретного элемента мы можем использовать поиск по наименованию (хотя в реальном коде этого стоит избегать в пользу поиска по коду или реквизитам):


// Возвращает тип "СправочникСсылка.Номенклатура"
СсылкаНаТовар = Справочники.Номенклатура.НайтиПоНаименованию("Мит 160 литр бойлер"); 
Запрос.УстановитьПараметр("Продукция", СсылкаНаТовар);

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


ПустаяСсылкаНоменклатуры = Справочники.Номенклатура.ПустаяСсылка();
Запрос.УстановитьПараметр("Номенклатура", ПустаяСсылкаНоменклатуры);

---

Как получить остатки по всей номенклатуре без фильтрации

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


Номенклатура = Справочники.Номенклатура.ПустаяСсылка();
Запрос.УстановитьПараметр("Номенклатура", Номенклатура);

При этом текст запроса выглядит следующим образом:


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &Период,
        Номенклатура = &Номенклатура И Склад = &Склад
    ) КАК ТоварыНаСкладахОстатки

В результате выполнения такого кода табличная часть останется пустой (или вернет некорректные данные). Разберем причину этого поведения.

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

Для того чтобы получить остатки по всей номенклатуре, необходимо полностью исключить условие фильтрации из параметров виртуальной таблицы остатков и секции ГДЕ:


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК ОстатокНаСкладе
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, Склад = &Склад) КАК ТоварыНаСкладахОстатки

В этом случае платформа наложит фильтр только по складу, а по измерению номенклатуры вернет абсолютно все имеющиеся на остатках позиции.

---

Реализация универсальных фильтров в запросах

На практике часто требуется создать универсальный алгоритм заполнения. Например, если пользователь выбрал конкретный товар на форме — заполнить остатки только по нему, а если поле выбора осталось пустым — получить остатки по всем товарам склада. Писать для этого два разных жестких запроса неэффективно.

Рассмотрим два профессиональных подхода к реализации этой задачи.

Способ А. Динамическое построение текста запроса (Рекомендуемый)

Этот способ является наиболее производительным с точки зрения СУБД. Мы анализируем, заполнен ли фильтр на форме, и динамически добавляем условие в текст запроса:


ТекстОтбораНоменклатуры = "";

// Проверяем, заполнено ли значение отбора на форме/в реквизитах
Если ЗначениеЗаполнено(ВыбраннаяНоменклатура) Тогда
    ТекстОтбораНоменклатуры = " И Номенклатура = &Номенклатура";
КонецЕсли;

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

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

Если ЗначениеЗаполнено(ВыбраннаяНоменклатура) Тогда
    Запрос.УстановитьПараметр("Номенклатура", ВыбраннаяНоменклатура);
КонецЕсли;

Результат = Запрос.Выполнить().Выгрузить();

Преимущество этого метода в том, что в базу данных уходит максимально простой и оптимизированный SQL-запрос. СУБД строит оптимальный план выполнения (Query Plan), используя индексы по измерению Номенклатура.

Способ Б. Использование логического условия в параметрах виртуальной таблицы

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


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

Запрос.УстановитьПараметр("Период", Новый Граница(ДатаОкончания, ВидГраницы.Включая));
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
Запрос.УстановитьПараметр("Номенклатура", ВыбраннаяНоменклатура); // Может быть пустой ссылкой

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

Внимание! Использование конструкции ИЛИ в условиях запроса на больших объемах данных (сотни тысяч или миллионы строк в регистре) может привести к ухудшению производительности СУБД (операция Index Scan вместо Index Seek). Для высоконагруженных систем всегда используйте Способ А.

---

Особенности работы с предопределенными элементами

Обратим внимание на строку из сообщения автора: Склад = Справочники.Склады.Склад;. Если склад является предопределенным элементом конфигурации, к нему можно обратиться напрямую из кода. Однако, если данный склад нужен исключительно для фильтрации внутри запроса, хорошим тоном разработки считается обращение к нему через специальную функцию запроса ПредопределенноеЗначение. Это позволяет разгрузить код от лишней установки параметров:


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК ОстатокНаСкладе
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(
        &Период, 
        Склад = ПредопределенноеЗначение("Справочник.Склады.Склад")
    ) КАК ТоварыНаСкладахОстатки

Использование функции ПредопределенноеЗначение делает структуру запроса более прозрачной и исключает необходимость вызова метода Запрос.УстановитьПараметр() для предопределенных элементов метаданных.

---

Правильная работа с датами, границами и моментом времени

Для корректного расчета остатков критически важно правильно указывать параметр &Период. В сообщениях темы автор использует объект Граница:


Запрос.УстановитьПараметр("Период", Новый Граница(ДатаОкончания, ВидГраницы.Включая));

Разберем три основных сценария работы со временем при получении остатков в 1С:

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

---

Загрузка результатов запроса в табличную часть

Для отображения полученных данных в табличной части формы автор использует метод Загрузить():

Объект.ПрайсЛист.Загрузить(РезультатЗапроса);

Рассмотрим правила успешного применения этого метода, чтобы избежать ситуации, когда "ошибки нет, но данные не отображаются":

---

Финальный чистый код заполнения остатков

Подведем итог и соберем идеальную серверную процедуру для заполнения табличной части остатками всей номенклатуры по складу без "мусорного" неиспользуемого кода (о котором справедливо предупреждали участники форума в сообщениях 17 и 21):


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

Изучив эти правила и механизмы, вы сможете писать надежные, быстродействующие запросы для работы с остатками товаров в любых конфигурациях на платформе 1С:Предприятие 8.

← На главную