При интеграции баз данных на платформе 1С:Предприятие 7.7 через механизм OLE Automation разработчики часто сталкиваются с ошибками при попытке выполнить запрос с условиями по переменным. Типичная ошибка выглядит следующим образом: Ошибка в выражении 'ТекСотрудникОЛЕ' или Переменная не объявлена как массив.
Разберем подробно причины возникновения этой ошибки и выясним, как правильно строить запросы к данным удаленной информационной базы.
Проанализируем контекст выполнения кода. Когда мы создаем объект Запрос через OLE-соединение:
ЗапросОЛЕ = ОЛЕ.CreateObject("Запрос");
Этот объект создается и компилируется строго во внутреннем контексте сервера OLE-базы. Локальные переменные базы-клиента (инициатора подключения) физически недоступны для парсера запроса внутри удаленной базы. Если написать в тексте запроса конструкцию вида |Условие(Объект = ТекСотрудникОЛЕ);, синтаксический анализатор удаленной базы не найдет переменную ТекСотрудникОЛЕ в своем локальном пространстве имен и выдаст ошибку.
Кроме того, язык запросов 1С 7.7 накладывает жесткие синтаксические ограничения:
Условие(...) нельзя использовать обращение к элементам массивов по индексу (например, СписокВР[1] вызовет синтаксическую ошибку).ExecuteBatch выполняет только исполняемые операторы, объявить через него новую переменную с директивой Перем в рантайме невозможно, так как переменные компилируются при инициализации модуля.Рассмотрим основной способ передачи параметров в OLE-запрос. Чтобы удаленный объект Запрос увидел значение фильтра, его необходимо поместить в глобальную экспортную переменную удаленной базы.
В большинстве типовых конфигураций 1С 7.7 (включая конфигурации по расчету зарплаты и учету) в Глобальном модуле уже присутствуют предопределенные служебные экспортные переменные, такие как глРасшифровка или глФильтр. Если конфигурация позволяет внесение изменений, можно объявить собственные экспортные переменные:
// В Глобальном модуле базы-источника (OLE-базы):
Перем глСотрудникДляЗапроса Экспорт;
Перем глСписокВР Экспорт;
Посмотрим на пример кода в базе-клиенте, использующий глобальную переменную:
// Поиск сотрудника в OLE-базе
СпрСотрудникиОЛЕ = ОЛЕ.CreateObject("Справочник.Сотрудники");
Если СпрСотрудникиОЛЕ.НайтиПоКоду(ТекСотрудник.Код, 0) = 1 Тогда
// Записываем ссылку напрямую в экспортную переменную OLE-базы
ОЛЕ.глРасшифровка = СпрСотрудникиОЛЕ.ТекущийЭлемент();
КонецЕсли;
// Создание и наполнение списка значений в контексте OLE-базы
СписокВРОЛЕ = ОЛЕ.CreateObject("СписокЗначений");
СписокВРОЛЕ.ДобавитьЗначение(ОЛЕ.ВидРасчета.ОтпускОчередной);
СписокВРОЛЕ.ДобавитьЗначение(ОЛЕ.ВидРасчета.ОтпускДополнительный);
СписокВРОЛЕ.ДобавитьЗначение(ОЛЕ.ВидРасчета.КомпенсацияОтпускаСверх28дней);
СписокВРОЛЕ.ДобавитьЗначение(ОЛЕ.ВидРасчета.КомпенсацияОтпуска);
// Помещаем список во вторую доступную глобальную переменную или используем свойства
ОЛЕ.глФильтр = СписокВРОЛЕ;
ЗапросОЛЕ = ОЛЕ.CreateObject("Запрос");
ТекстЗапросаОЛЕ = "Период с '" + ДатаНачалаОтпускаОЛЕ + "' по '" + ДатаОкончанияОтпускаОЛЕ + "';
|ТекущийДокумент = ЖурналРасчетов.Зарплата.ТекущийДокумент;
|ДатаНачала = ЖурналРасчетов.Зарплата.ДатаНачала;
|ДатаОкончания = ЖурналРасчетов.Зарплата.ДатаОкончания;
|Объект = ЖурналРасчетов.Зарплата.Объект;
|ВидРасч = ЖурналРасчетов.Зарплата.ВидРасч;
|Дни = ЖурналРасчетов.Зарплата.Дни;
|Результат = ЖурналРасчетов.Зарплата.Результат;
|Функция ДниСумма = Сумма(Дни);
|Функция РезультатВсего = Сумма(Результат);
|Группировка Объект;
|Группировка ВидРасч;
|Группировка ТекущийДокумент;
|Группировка ДатаНачала;
|Группировка ДатаОкончания;
|Условие(Объект = глРасшифровка);
|Условие(ВидРасч в глФильтр);
|";
Если ЗапросОЛЕ.Выполнить(ТекстЗапросаОЛЕ) = 1 Тогда
Пока ЗапросОЛЕ.Группировка(1) = 1 Цикл
// Обработка результатов
КонецЦикла;
КонецЕсли;
Разберем альтернативный вариант. Если требуется получить записи только по одному конкретному сотруднику, обращение через объект Запрос по OLE может быть избыточным и медленным. В объекте ЖурналРасчетов платформы 1С 7.7 предусмотрены встроенные методы позиционирования, которые работают значительно быстрее:
ЖР_ОЛЕ = ОЛЕ.CreateObject("ЖурналРасчетов.Зарплата");
СпрСотрудникиОЛЕ = ОЛЕ.CreateObject("Справочник.Сотрудники");
Если СпрСотрудникиОЛЕ.НайтиПоКоду(ТекСотрудник.Код, 0) = 1 Тогда
СотрОЛЕ = СпрСотрудникиОЛЕ.ТекущийЭлемент();
// Выбираем записи по объекту за указанный интервал дат
ЖР_ОЛЕ.ВыбратьПериодПоОбъекту(СотрОЛЕ, ДатаНачалаОтпуска, ДатаОкончанияОтпуска);
Пока ЖР_ОЛЕ.ПолучитьЗапись() = 1 Цикл
ВР_ОЛЕ = ЖР_ОЛЕ.ВидРасч;
Если (ВР_ОЛЕ = ОЛЕ.ВидРасчета.ОтпускОчередной)
ИЛИ (ВР_ОЛЕ = ОЛЕ.ВидРасчета.ОтпускДополнительный)
ИЛИ (ВР_ОЛЕ = ОЛЕ.ВидРасчета.КомпенсацияОтпуска) Тогда
ДниОтпуска = ЖР_ОЛЕ.Дни;
СуммаОтпуска = ЖР_ОЛЕ.Результат;
// Обработка полученных значений
КонецЕсли;
КонецЦикла;
КонецЕсли;
Рассмотрим наиболее производительный подход при больших объемах данных. При выполнении запроса через OLE-соединение каждый вызов методов Группировка() и обращение к реквизитам выборки приводит к отдельному сетевому/межпроцессному вызову (COM Marshaling), что существенно замедляет выполнение кода.
Оптимальным решением является:
.ert), в которой запрос формируется и обрабатывается локально внутри OLE-базы.ТаблицуЗначений.ЗначениеВСтрокуВнутр().ЗначениеИзСтрокиВнутр().Такой подход полностью устраняет проблемы с контекстом переменных в запросе и ускоряет передачу данных в десятки раз.