При интеграции устаревших конфигураций на платформе 1С 7.7 (например, "Торговля и Склад 7.7") с современными базами на платформе 1С 8.3 разработчики часто используют COM-соединение (OLE). Одним из наиболее узких мест при таком подходе является построение и выполнение запросов на стороне 1С 7.7 из кода 1С 8.3. Синтаксис "черных запросов" 1С 7.7 существенно отличается от языка запросов 1С 8.x, что приводит к неочевидным ошибкам, когда запрос возвращает пустой результат или нулевые остатки, хотя в самой семерке отчеты показывают корректные данные.
В этой статье мы подробно разберем, почему возникают такие проблемы, исправим синтаксические ошибки в условиях запроса 1С 7.7, научимся правильно передавать ссылочные параметры и оптимизируем обход результатов для достижения максимального быстродействия.
---
Рассмотрим первоначальный код запроса, с которым столкнулся автор темы, и проанализируем основные ошибки, из-за которых функция КонОст() возвращала «0»:
// Фрагмент проблемного запроса
|ФирмаКод = Регистр.ОстаткиТМЦ.Фирма.Код;
|НоменклатураР = Регистр.ОстаткиТМЦ.Номенклатура.Родитель;
|...
|Условие (ФирмаКод = '00017');
|Условие (НоменклатураР = 'р2649');
Выясним основные причины неработоспособности такого запроса:
НоменклатураР принимает значение типа «СправочникСсылка.Номенклатура» (Родитель). В условии происходит попытка сравнить эту ссылку со строковым литералом 'р2649'. В 1С 7.7 такое сравнение всегда будет ложным, из-за чего выборка окажется пустой.'00017' в условиях запроса 1С 7.7 часто интерпретируется системой как дата или некорректный литерал, особенно при передаче через COM. В запросах 1С 7.7 строковые константы необходимо оборачивать в двойные кавычки. В коде 1С 8.3 это выглядит как удвоенные кавычки внутри строки.---
Для того чтобы запрос 1С 7.7 успешно отработал, перепишем условия. Вместо сравнения объектов со строками будем сравнивать их строковые коды, а также заменим апострофы на двойные кавычки. Обязательно добавим явное указание периода.
Разберем пример формирования текста запроса на стороне 1С 8.3:
// Формируем дату в формате 1С 7.7 (ДД.ММ.ГГГГ или строка вида 'ДД.ММ.ГГГГ')
ДатаЗапросаТекст = "'" + Формат(Объект.ДатаОстатков, "ДФ=dd.MM.yyyy") + "'";
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Период с " + ДатаЗапросаТекст + " по " + ДатаЗапросаТекст + ";
|Фирма = Регистр.ОстаткиТМЦ.Фирма;
|ФирмаКод = Регистр.ОстаткиТМЦ.Фирма.Код;
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|НоменклатураРодительКод = Регистр.ОстаткиТМЦ.Номенклатура.Родитель.Код;
|Склад = Регистр.ОстаткиТМЦ.Склад;
|СкладКод = Регистр.ОстаткиТМЦ.Склад.Код;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка Номенклатура без групп;
|Группировка Склад без групп;
|Условие (ФирмаКод = ""00017"");
|Условие (НоменклатураРодительКод = ""р2649"");
|Условие (СкладКод = ""00060"");
|//}}ЗАПРОС";
Важные детали:
""00017"" в коде 1С 8.3 скомпилируется в строковый литерал "00017" (с двойными кавычками) внутри отправляемого запроса.без групп. Это исключает иерархические папки из результатов выборки, оставляя только конечные элементы номенклатуры, что значительно упрощает обработку данных.---
Что делать, если отбор по коду нам не подходит (например, коды неуникальны или группа номенклатуры выбирается динамически на стороне 1С 8.3 как объект)? Передать непосредственно ссылку 1С 8.3 в условие запроса 1С 7.7 невозможно.
Рассмотрим элегантное решение, предложенное в сообщении 65: передача ссылки через механизм сериализации ЗначениеИзФайла.
Алгоритм действий выглядит следующим образом:
ЗначениеВФайл.ЗначениеИзФайла, которая считает объект прямо в контекст выполнения запроса.Посмотрим на программную реализацию этого метода:
// Инициализируем объекты OLE базы 7.7
СпрНоменклатура77 = База77.CreateObject("Справочник.Номенклатура");
// Ищем целевую группу номенклатуры по коду "р2649"
Если СпрНоменклатура77.НайтиПоКоду("р2649") = 0 Тогда
ВызватьИсключение "Группа номенклатуры не найдена в базе 1С 7.7!";
КонецЕсли;
// Получаем ссылку на текущий найденный элемент
ГруппаОбъект77 = СпрНоменклатура77.ТекущийЭлемент();
// Формируем путь к временному файлу
ИмяФайлаПередачи = База77.КаталогВременныхФайлов() + "v7_group_filter.val";
// Сохраняем объект во временный файл (третий параметр "1" обязателен для сохранения ссылочных данных)
База77.ЗначениеВФайл(ИмяФайлаПередачи, ГруппаОбъект77, 1);
// Строим запрос с использованием сохраненного файла в условии
ДатаЗапросаТекст = "'" + Формат(Объект.ДатаОстатков, "ДФ=dd.MM.yyyy") + "'";
ТекстЗапроса =
"Период с " + ДатаЗапросаТекст + " по " + ДатаЗапросаТекст + ";
|Номенклатура = Регистр.ОстаткиТМЦ.Номенклатура;
|НоменклатураРодитель = Регистр.ОстаткиТМЦ.Номенклатура.Родитель;
|СкладКод = Регистр.ОстаткиТМЦ.Склад.Код;
|Количество = Регистр.ОстаткиТМЦ.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка Номенклатура без групп;
|Условие (СкладКод = ""00060"");
|Условие (НоменклатураРодитель = ЗначениеИзФайла(""" + ИмяФайлаПередачи + """));";
Запрос77 = База77.CreateObject("Запрос");
Если Запрос77.Выполнить(ТекстЗапроса) = 0 Тогда
Предупреждение("Не удалось выполнить запрос в базе 1С 7.7");
Возврат;
КонецЕсли;
---
Использование вложенных циклов обхода группировок запроса через COM-соединение, как в примере автора:
Пока Запрос.Группировка("Номенклатура") = 1 Цикл
Пока Запрос.Группировка("Склад") = 1 Цикл
// Обращение к свойствам объекта через COM в цикле
Наименование = Запрос.Номенклатура.Наименование;
КонецЦикла;
КонецЦикла;
Это классический антипаттерн интеграции. Каждое обращение к методу Группировка() или к реквизиту объекта Запрос.Номенклатура.Наименование порождает сетевой вызов (круглый вызов между процессами — roundtrip). При выборке в несколько тысяч позиций такой код будет выполняться чрезвычайно медленно (до нескольких десятков минут).
Чтобы ускорить процесс в десятки раз, применим метод выгрузки результатов во временную Таблицу Значений на стороне 1С 7.7. Обход плоской таблицы значений работает в разы быстрее, так как не требует обращения к иерархическим группировкам объекта запроса.
Проанализируем правильный алгоритм обработки результата:
// Выполняем запрос
Если Запрос77.Выполнить(ТекстЗапроса) = 1 Тогда
// Создаем объект ТаблицаЗначений в контексте 1С 7.7
ТЗ77 = База77.CreateObject("ТаблицаЗначений");
// Выгружаем результаты запроса в плоскую таблицу (второй и третий параметры равны 0)
Запрос77.Выгрузить(ТЗ77, 0, 0);
// Получаем количество строк
КоличествоСтрок = ТЗ77.КоличествоСтрок();
// Обходим плоскую таблицу значений в одном цикле на стороне 1С 8.3
Для НомерСтроки = 1 По КоличествоСтрок Цикл
// Получаем ссылку на номенклатуру 7.7
НоменклатураСсылка77 = ТЗ77.ПолучитьЗначение(НомерСтроки, "Номенклатура");
// Быстро считываем реквизиты объекта
Наименование = НоменклатураСсылка77.Наименование;
Код77 = НоменклатураСсылка77.Код;
Остаток = ТЗ77.ПолучитьЗначение(НомерСтроки, "КоличествоКонОст");
// Заполняем таблицу значений или табличную часть в 1С 8.3
НоваяСтрока = ТаблицаОстатков83.Добавить();
НоваяСтрока.Номенклатура77_Наименование = Наименование;
НоваяСтрока.Номенклатура77_Код = Код77;
НоваяСтрока.Остаток = Остаток;
КонецЦикла;
КонецЕсли;
Использование этого подхода позволяет сократить время переноса данных по остаткам склада с нескольких минут до нескольких секунд.