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

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

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

Подключение к сторонней информационной базе через ODBCDataBase

Для выполнения прямых запросов к внешней базе данных компонента 1С++ предоставляет объект ODBCDataBase и метод ПрисоединитьИБ. Этот метод инициализирует подключение к SQL-серверу и загружает метаданные удаленной конфигурации, что позволяет парсеру 1С++ корректно разрешать метаимена (такие как $Справочник.Сотрудники, $ПоследнееЗначение и т.д.).

Рассмотрим типовую схему инициализации соединения и привязки его к объекту ODBCRecordset:


ODBCD = СоздатьОбъект("ODBCDataBase");
// ПутьИБ должен заканчиваться символом обратного слэша "\"
ODBCD.ПрисоединитьИБ(КаталогСтороннейИБ, ИмяПользователя, Пароль);

RS = СоздатьОбъект("ODBCRecordset");
RS.УстБД(ODBCD);

После выполнения метода RS.УстБД(ODBCD) все запросы, отправляемые через данный экземпляр ODBCRecordset, будут транслироваться в контексте внешней базы данных.

Почему не работает прямое сравнение идентификаторов между базами

Проанализируем частую ошибку: передачу ссылок или внутренних ID из текущей базы во временную таблицу внешней базы. В 1С 7.7 внутренние девятизначные идентификаторы формируются независимо в каждой информационной базе. Даже если элементы справочников в обеих базах имеют одинаковые наименования или коды, их системные ID в 99% случаев различаются.

Также важно учитывать формат хранения периодических реквизитов:

  1. В таблице периодических значений _1SCONST реквизиты строгого типа хранятся как 9-значная строка CHAR(9) с внутренним ID объекта.
  2. Метод ЗначениеВДлиннуюСтрокуБД формирует 13-значную строку (содержащую префикс типа), которая используется только для реквизитов составного типа («Неопределенный»). Если сравнить макрос $ПоследнееЗначение для типизированного реквизита с 13-значным значением, запрос вернет пустой результат из-за несовпадения длины строк.
  3. Стандартный метод RS.УложитьСписокОбъектов при передаче третьего параметра (вида справочника) пытается развернуть группы на основе контекста текущей сессии 1С, что неприменимо к объектам удаленной базы.

Вариант 1. Отбор по кодам справочника с разворачиванием иерархии на стороне SQL

Если в обеих базах поддерживается синхронность кодов справочников (например, справочника «Департаменты» или «Подразделения»), наиболее надежным и быстрым подходом является передача списка кодов и поиск элементов непосредственно средствами SQL без использования медленного OLE-соединения.

Разберем по шагам реализацию данного варианта:


// 1. Формируем список кодов из выбранных на форме элементов
СписокКодов = СоздатьОбъект("СписокЗначений");
Для К = 1 По СписокФормы.РазмерСписка() Цикл
    Эл = СписокФормы.ПолучитьЗначение(К);
    СписокКодов.ДобавитьЗначение(СокрЛП(Эл.Код));
КонецЦикла;

// 2. Инициализируем соединение и укладываем список кодов во временную таблицу
ODBCD = СоздатьОбъект("ODBCDataBase");
ODBCD.ПрисоединитьИБ(КаталогСтороннейИБ, ИмяПользователя, Пароль);

RS = СоздатьОбъект("ODBCRecordset");
RS.УстБД(ODBCD);

// Укладываем список без третьего параметра - как строковые значения
RS.УложитьСписокОбъектов(СписокКодов, "#КодыОтбора");
RS.УстановитьТекстовыйПараметр("ВыбДата", КонДата);

// 3. Выполняем запрос с выборкой ID элементов удаленной базы по кодам
ТекстЗапроса = "
|SELECT 
|    Сотрудники.ID [Сотр $Справочник.Сотрудники]
|FROM 
|    $Справочник.Сотрудники AS Сотрудники WITH (NOLOCK)
|WHERE 
|    Сотрудники.IsFolder = 2 
|    AND Сотрудники.IsMark = 0
|    AND $ПоследнееЗначение.Сотрудники.Департамент(Сотрудники.ID, :ВыбДата) IN (
|        SELECT Деп.ID 
|        FROM $Справочник.Департаменты AS Деп WITH (NOLOCK)
|        WHERE LTRIM(RTRIM(Деп.CODE)) IN (SELECT Val FROM #КодыОтбора)
|    )
|";

ТЗРезультат = RS.ВыполнитьИнструкцию(ТекстЗапроса);

Вариант 2. Двухэтапный отбор с учетом иерархии групп

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

Рассмотрим пошаговый алгоритм:

  1. Собрать коды выбранных групп и элементов из экранной формы.
  2. Выполнить предварительный прямой запрос к удаленной базе, который находит все подчиненные элементы с условием IsFolder = 2, относящиеся к указанным группам (по реквизиту PARENTID или через рекурсивный запрос).
  3. Полученные 9-значные ID уложить во временную таблицу #Департаменты.
  4. Выполнить основной запрос по сотрудникам, связав периодический реквизит со сформированной временной таблицей.

// Шаг 1. Получаем список ID всех подчиненных элементов в удаленной базе
ТекстПредварительный = "
|SELECT DISTINCT 
|    Деп.ID AS ID
|FROM 
|    $Справочник.Департаменты AS Деп WITH (NOLOCK)
|WHERE 
|    Деп.IsFolder = 2
|    AND Деп.IsMark = 0
|    AND (
|        LTRIM(RTRIM(Деп.CODE)) IN (SELECT Val FROM #КодыОтбора)
|        OR Деп.ParentID IN (
|            SELECT Родитель.ID 
|            FROM $Справочник.Департаменты AS Родитель WITH (NOLOCK)
|            WHERE LTRIM(RTRIM(Родитель.CODE)) IN (SELECT Val FROM #КодыОтбора)
|        )
|    )
|";

ТЗПодчиненные = RS.ВыполнитьИнструкцию(ТекстПредварительный);

// Шаг 2. Выгружаем полученные 9-значные ID во временную таблицу
СписокID = СоздатьОбъект("СписокЗначений");
ТЗПодчиненные.Выгрузить(СписокID, , "ID");

RS.УложитьСписокОбъектов(СписокID, "#Департаменты");

// Шаг 3. Основной запрос по сотрудникам
ТекстОсновной = "
|SELECT 
|    Сотрудники.ID [Сотр $Справочник.Сотрудники]
|FROM 
|    $Справочник.Сотрудники AS Сотрудники WITH (NOLOCK)
|WHERE 
|    Сотрудники.IsFolder = 2 
|    AND Сотрудники.IsMark = 0
|    AND $ПоследнееЗначение.Сотрудники.Департамент(Сотрудники.ID, :ВыбДата) IN (
|        SELECT Val FROM #Департаменты
|    )
|";

ТЗФинальная = RS.ВыполнитьИнструкцию(ТекстОсновной);

Важные практические рекомендации

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

  1. Сохранение контекста соединения: Временные таблицы (начинающиеся с #) создаются в рамках конкретной сессии SQL-соединения. Не пересоздавайте объект ODBCRecordset между укладкой списка и выполнением основного запроса, иначе временная таблица будет потеряна.
  2. Длина строковых идентификаторов: Внутренние идентификаторы объектов в 1С 7.7 имеют фиксированную длину 9 символов и содержат пробелы. Если передаются готовые ID, нельзя удалять концевые или ведущие пробелы функцией СокрЛП, иначе сравнение строк в SQL вернет FALSE.
  3. Отказ от OLE в цикле: Использование OLE-сервера для перебора элементов и поиска их идентификаторов через MetaDataWork в цикле многократно замедляет работу. Прямой SQL-запрос по кодам справочника выполняется мгновенно и не создает лишних блокировок.
← На главную