При интеграции или сборе консолидированной отчетности в среде 1С:Предприятие 7.7 нередко возникает задача прямого чтения данных из сторонней информационной базы через компоненту 1С++. Особые сложности появляются тогда, когда требуется отфильтровать выборку по периодическим реквизитам или подчиненным справочникам, используя список значений, сформированный в текущей базе.
Разберем детально, как правильно подключиться к сторонней базе, почему возникает расхождение внутренних идентификаторов и как корректно передать список элементов или групп для фильтрации.
Для выполнения прямых запросов к внешней базе данных компонента 1С++ предоставляет объект ODBCDataBase и метод ПрисоединитьИБ. Этот метод инициализирует подключение к SQL-серверу и загружает метаданные удаленной конфигурации, что позволяет парсеру 1С++ корректно разрешать метаимена (такие как $Справочник.Сотрудники, $ПоследнееЗначение и т.д.).
Рассмотрим типовую схему инициализации соединения и привязки его к объекту ODBCRecordset:
ODBCD = СоздатьОбъект("ODBCDataBase");
// ПутьИБ должен заканчиваться символом обратного слэша "\"
ODBCD.ПрисоединитьИБ(КаталогСтороннейИБ, ИмяПользователя, Пароль);
RS = СоздатьОбъект("ODBCRecordset");
RS.УстБД(ODBCD);
После выполнения метода RS.УстБД(ODBCD) все запросы, отправляемые через данный экземпляр ODBCRecordset, будут транслироваться в контексте внешней базы данных.
Проанализируем частую ошибку: передачу ссылок или внутренних ID из текущей базы во временную таблицу внешней базы. В 1С 7.7 внутренние девятизначные идентификаторы формируются независимо в каждой информационной базе. Даже если элементы справочников в обеих базах имеют одинаковые наименования или коды, их системные ID в 99% случаев различаются.
Также важно учитывать формат хранения периодических реквизитов:
_1SCONST реквизиты строгого типа хранятся как 9-значная строка CHAR(9) с внутренним ID объекта.ЗначениеВДлиннуюСтрокуБД формирует 13-значную строку (содержащую префикс типа), которая используется только для реквизитов составного типа («Неопределенный»). Если сравнить макрос $ПоследнееЗначение для типизированного реквизита с 13-значным значением, запрос вернет пустой результат из-за несовпадения длины строк.RS.УложитьСписокОбъектов при передаче третьего параметра (вида справочника) пытается развернуть группы на основе контекста текущей сессии 1С, что неприменимо к объектам удаленной базы.Если в обеих базах поддерживается синхронность кодов справочников (например, справочника «Департаменты» или «Подразделения»), наиболее надежным и быстрым подходом является передача списка кодов и поиск элементов непосредственно средствами 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.ВыполнитьИнструкцию(ТекстЗапроса);
Если пользователь выбирает в отчете группы справочника, простого сравнения по кодам элементов недостаточно — необходимо включить в отбор всех потомков выбранных папок. Для этого оптимально использовать предварительный запрос к удаленной базе.
Рассмотрим пошаговый алгоритм:
IsFolder = 2, относящиеся к указанным группам (по реквизиту PARENTID или через рекурсивный запрос).ID уложить во временную таблицу #Департаменты.
// Шаг 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.ВыполнитьИнструкцию(ТекстОсновной);
При построении запросов к внешним базам обратите внимание на следующие технические аспекты:
#) создаются в рамках конкретной сессии SQL-соединения. Не пересоздавайте объект ODBCRecordset между укладкой списка и выполнением основного запроса, иначе временная таблица будет потеряна.ID, нельзя удалять концевые или ведущие пробелы функцией СокрЛП, иначе сравнение строк в SQL вернет FALSE.MetaDataWork в цикле многократно замедляет работу. Прямой SQL-запрос по кодам справочника выполняется мгновенно и не создает лишних блокировок.