Почему не возвращаются остатки в бухгалтерском запросе через COM-соединение в 1С?

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

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

Анализ проблемы: почему запрос возвращает пустой результат

Рассмотрим типовой сценарий, в котором формируется COM-запрос к удаленной базе для выборки остатков товаров по регистру бухгалтерии Хозрасчетный:


СписокНоменклатуры = V8.NewObject("СписокЗначений");
Для каждого ОстатокНоменклатуры из ОстаткиНоменклатуры Цикл
    НайденнаяНоменклатура = V8.Справочники.Номенклатура.НайтиПоКоду(ОстатокНоменклатуры.Код);
    Если НЕ НайденнаяНоменклатура.Пустая() Тогда
        СписокНоменклатуры.Добавить(НайденнаяНоменклатура.Ref);
    КонецЕсли;
КонецЦикла;

олеЗапрос = V8.NewObject("Query");
олеЗапрос.Текст = 
    "ВЫБРАТЬ
    |    ХозрасчетныйОстатки.Субконто1.Код КАК Код,
    |    ХозрасчетныйОстатки.Субконто1.Ссылка КАК Товар,
    |    ХозрасчетныйОстатки.КоличествоОстатокДт КАК Остаток
    |ИЗ
    |    РегистрБухгалтерии.Хозрасчетный.Остатки(
    |            &Дата,
    |            Счет = &Счет,
    |            &ВидыСубконто,
    |            Организация = &Организация
    |                И Субконто1 В (&Товары)) КАК ХозрасчетныйОстатки
    |ГДЕ
    |    ХозрасчетныйОстатки.КоличествоОстатокДт > 0";

олеЗапрос.УстановитьПараметр("Счет", V8.ПланыСчетов.Хозрасчетный.НайтиПоНаименованию("ТоварыНаСкладах"));
олеЗапрос.УстановитьПараметр("ВидыСубконто", V8.ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.НайтиПоНаименованию("Номенклатура"));
олеЗапрос.УстановитьПараметр("Организация", V8.Справочники.Организации.НайтиПоРеквизиту("ИНН", ОрганизацияКонтрагент.ИНН));
олеЗапрос.УстановитьПараметр("Товары", СписокНоменклатуры);
олеЗапрос.УстановитьПараметр("Дата", ДатаКон);
олеОстаткиНоменклатуры = олеЗапрос.Выполнить().Выгрузить();

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

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

Способы корректной передачи счета в запрос

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

Вариант 1. Поиск по коду счета

Если код счета фиксирован (например, 41.01), можно использовать метод поиска по коду:


СчетТовары = V8.ПланыСчетов.Хозрасчетный.НайтиПоКоду("41.01");
олеЗапрос.УстановитьПараметр("Счет", СчетТовары);

Вариант 2. Обращение к предопределенному счету через контекст COM

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


// Обращение через свойство менеджера
СчетТовары = V8.ПланыСчетов.Хозрасчетный.ТоварыНаСкладах;
олеЗапрос.УстановитьПараметр("Счет", СчетТовары);

// Либо получение через функцию глобального контекста удаленной базы
СчетТовары = V8.ПредопределенноеЗначение("ПланСчетов.Хозрасчетный.ТоварыНаСкладах");
олеЗапрос.УстановитьПараметр("Счет", СчетТовары);

Вариант 3. Использование конструкции ЗНАЧЕНИЕ прямо в тексте запроса

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


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

Проблема разной длины кода при чтении из Субконто1

При обращении к свойству ХозрасчетныйОстатки.Субконто1.Код разработчики часто сталкиваются с ситуацией, когда возвращенный код номенклатуры содержит в конце множество пробелов, а его длина составляет 25–30 символов вместо ожидаемых 9 или 12.

Выясним причину: измерение Субконто1 в регистре бухгалтерии имеет составной тип данных (ссылки на различные справочники, входящие в план видов характеристик). Когда платформа выполняет неявное соединение через разыменование точки, СУБД формирует строковое поле с максимальной длиной кода среди всех возможных таблиц субконто, дополняя строку пробелами до максимального размера фиксированной строки (тип CHAR/NCHAR).

Чтобы получить точное значение кода без лишней длины, применим явное приведение типа с помощью функции ВЫРАЗИТЬ прямо в запросе:

ВЫРАЗИТЬ(ХозрасчетныйОстатки.Субконто1 КАК Справочник.Номенклатура).Код КАК Код

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

Оптимизация передачи отбора по номенклатуре

Чтобы не выполнять медленный построчный поиск в цикле через COM-соединение, оптимизируем код: передадим в COM-базу простой массив строковых кодов и наложим условие прямо внутри виртуальной таблицы остатков.

Посмотрим на итоговый оптимизированный пример:


// Формируем простой массив строк в локальной базе
МассивКодов = Новый Массив;
Для каждого Стр Из ОстаткиНоменклатуры Цикл
    МассивКодов.Добавить(Стр.Код);
КонецЦикла;

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

// Примитивные типы (массивы, строки, даты) передаются через COM без создания тяжелых прокси-объектов
олеЗапрос.УстановитьПараметр("МассивКодов", МассивКодов);
олеЗапрос.УстановитьПараметр("Дата", ДатаКон);
олеЗапрос.УстановитьПараметр("Организация", V8.Справочники.Организации.НайтиПоРеквизиту("ИНН", ОрганизацияКонтрагент.ИНН));

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

В этом решении счет и вид субконто зафиксированы через конструкцию ЗНАЧЕНИЕ, что полностью исключает ошибки поиска объектов удаленной базы по строковым наименованиям, а фильтрация по массиву кодов выполняется на стороне сервера удаленной базы за один вызов.

← На главную