При разработке отчетов и интеграций в 1С:Предприятии 7.7 с использованием внешней компоненты 1С++ и класса ODBCRecordset разработчики часто сталкиваются с задачей сопоставления данных текущей базы со сторонней базой данных SQL. В процессе решения этой задачи возникают типичные проблемы: некорректная работа метода УложитьСписокОбъектов() при передаче строк, ошибки парсера метаданных при кросс-базовых запросах и чтение периодических реквизитов. Разберем подробно причины этих проблем и пошаговые варианты их решения.
Проанализируем механику работы метода УложитьСписокОбъектов(). Данный метод предназначен исключительно для выгрузки ссылочных типов данных 1С:Предприятия 7.7 (элементы справочников, групп или документов). В СУБД метод формирует таблицу с фиксированной структурой колонок: val CHAR(9) (внутренний идентификатор объекта базы данных) и isfolder INT (признак группы).
Если в СписокЗначений поместить примитивные типы (например, строки с артикулами или числа), метод пытается преобразовать их во внутренний девятисимвольный ID объекта (id9). Так как строки не являются ссылками, метод не может получить идентификатор и записывает в результирующую таблицу одну служебную строку с пустым значением поля val и флагом isfolder = 2 (маркер отсутствия валидных ссылок).
Для передачи строковых списков (артикулов, кодов, наименований) во временную таблицу SQL существуют другие надежные подходы.
Рассмотрим, как можно передать произвольный список артикулов в SQL-запрос:
ТаблицаЗначений, в которой создается колонка с нужной строковой длиной. Затем на стороне SQL создается структура временной таблицы, и данные переносятся пакетной вставкой:
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Артикул", "Строка", 50);
// Заполняем ТЗ артикулами...
рс = СоздатьОбъект("ODBCRecordset");
рс.ВыполнитьИнструкцию("CREATE TABLE #ВТ_Артикулы (Артикул VARCHAR(50))");
рс.ВыполнитьSQL_ИзТЗ(ТЗ, "#ВТ_Артикулы");
# и заполняется порциями значений с помощью конструкции INSERT INTO #ВТ_Артикулы VALUES (...).'АРТ001', 'АРТ002' и подставить непосредственно в блок WHERE Таблица.Артикул IN (...).Справочник.МПЗ. В этом случае метод рс.УложитьСписокОбъектов(СписокСсылок, "#ВТ_МПЗ", "МПЗ") отработает штатно, а в самом запросе выполняется соединение временной таблицы со справочником для получения артикулов.Когда требуется соединить данные текущей базы и сторонней информационной базы (расположенной на том же SQL-сервере или на связанном сервере Linked Server), наиболее производительным решением является выполнение одного общего запроса с парсингом метаданных обеих конфигураций.
Разберем по шагам, как обработать метаданные двух разных баз с помощью объекта MetaDataWork:
$.@, с указанием полного пути к базе данных СУБД (ИмяБазы2.dbo.@Справочник.МПЗ).глМД.ОбрМетаСКЛ() для разрешения метаданных текущей базы. Символы @ при этом парсером игнорируются.MetaDataWork, к которому подключается файл метаданных сторонней базы: глМД2.ПрисоединитьМД(ПутьКБазе2 + "1Cv7.md").@ в тексте запроса заменяется на $ с помощью СтрЗаменить(), после чего текст передается во второй парсер: глМД2.ОбрМетаСКЛ().Посмотрим на практический пример реализации:
Процедура ВыполнитьСвязанныйЗапрос()
Перем рс, глМД, глМД2, ТекстЗапроса, ТЗ, ПутьКБазе2;
рс = СоздатьОбъект("ODBCRecordset");
ТекстЗапроса = "
|SELECT
| СпрНаш.DESCR AS НаименованиеВЭтойБазе,
| СпрУдал.DESCR AS НаименованиеВУдаленнойБазе,
| СпрНаш.CODE AS Артикул
|FROM $Справочник.МПЗ AS СпрНаш WITH (NOLOCK)
|LEFT JOIN dataBase2.dbo.@Справочник.МПЗ AS СпрУдал WITH (NOLOCK)
| ON LTRIM(RTRIM(СпрНаш.CODE)) = LTRIM(RTRIM(СпрУдал.@КодСистемы))
|";
// 1. Обработка метаданных текущей базы
глМД = СоздатьОбъект("MetaDataWork");
ТекстЗапроса = глМД.ОбрМетаСКЛ(ТекстЗапроса);
// 2. Обработка метаданных удаленной базы
глМД2 = СоздатьОбъект("MetaDataWork");
ПутьКБазе2 = "\\Server\1C_Bases\DataBase2\";
глМД2.ПрисоединитьМД(ПутьКБазе2 + "1Cv7.md");
// Подменяем маркер и парсим метаданные второй базы
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "@", "$");
ТекстЗапроса = глМД2.ОбрМетаСКЛ(ТекстЗапроса);
// 3. Выполнение готового SQL-запроса
ТЗ = рс.ВыполнитьИнструкцию(ТекстЗапроса);
ТЗ.ВыбратьСтроку();
КонецПроцедуры
При использовании прямого парсинга метаданных через ОбрМетаСКЛ() вызов рс.УстановитьТекстовыйПараметр() до обработки метаданных может приводить к ошибке Meta name parser error: не указан параметр. Выясним причину: метод ОбрМетаСКЛ() анализирует только текст и ничего не знает о параметрах, установленных в объекте ODBCRecordset.
Для корректной передачи параметров используем следующие правила:
глМД.ЗначениеВСтрокуБД(Элемент) и подставляем его строковое представление прямо в условие запроса.YYYYMMDD:
СтрДатаНач = Формат(НачДата, "ДГГГГММДД");
СтрДатаКон = Формат(КонДата, "ДГГГГММДД");
IDКонтрагент = глМД.ЗначениеВСтрокуБД(ВыбКонтрагент);
ТекстЗапроса = "
|SELECT ...
|FROM $Документ.РасходнаяНакладная AS РН WITH (NOLOCK)
|INNER JOIN _1SJOURN AS Журнал WITH (NOLOCK) ON РН.IDDOC = Журнал.IDDOC
|WHERE Журнал.DATE_TIME_IDDOC >= '" + СтрДатаНач + "'
| AND Журнал.DATE_TIME_IDDOC <= '" + СтрДатаКон + "Z'
| AND $РН.Контрагент = '" + IDКонтрагент + "'
|";
Конструкция парсера $ПоследнееЗначение.Справочник.Реквизит(...) формирует подзапрос к таблице периодических значений _1SCONST текущей базы. Если попытаться применить эту конструкцию к сторонней базе данных, подзапрос обратится к локальной таблице _1SCONST вместо таблицы удаленной базы и вернет NULL.
Для получения актуального значения периодического реквизита из сторонней базы пропишем подзапрос к таблице периодики вручную, явно указав имя внешней базы данных:
// В запросе связываем периодический реквизит из сторонней базы GM_teach
ТекстЗапроса = "
|SELECT
| Журнал.DOCNO AS НомерДок,
| $МПЗ1.КодСистемы AS Артикул,
| $РасходнаяНакладнаяСтроки.Количество AS Количество,
| (
| SELECT TOP 1 CAST(c_val.VALUE AS NUMERIC(18, 4))
| FROM GM_teach.dbo._1sconst AS c_val WITH (NOLOCK)
| WHERE c_val.ID = 1281
| AND c_val.OBJID = МПЗ.ID
| AND c_val.DATE <= '" + СтрДата + "'
| ORDER BY c_val.DATE DESC, c_val.TIME DESC, c_val.DOCID DESC, c_val.ROW_ID DESC
| ) AS УправленческаяСебестоимость
|FROM $Документ.РасходнаяНакладная AS РасходнаяНакладная WITH (NOLOCK)
|INNER JOIN $ДокументСтроки.РасходнаяНакладная AS РасходнаяНакладнаяСтроки WITH (NOLOCK)
| ON РасходнаяНакладная.IDDOC = РасходнаяНакладнаяСтроки.IDDOC
|INNER JOIN _1SJOURN AS Журнал WITH (NOLOCK)
| ON РасходнаяНакладная.IDDOC = Журнал.IDDOC
|INNER JOIN $Справочник.МПЗ AS МПЗ1 WITH (NOLOCK)
| ON РасходнаяНакладнаяСтроки.МПЗ = МПЗ1.ID
|INNER JOIN GM_teach.dbo.@Справочник.МПЗ AS МПЗ
| ON LTRIM(RTRIM(@МПЗ.КодСистемы)) = LTRIM(RTRIM($МПЗ1.КодСистемы))
|";
Идентификатор реквизита удаленной базы (в примере — 1281) можно определить заранее в конфигураторе сторонней базы либо программно через объект глМД2.ИДРеквизита("Справочник.МПЗ.УправленческаяСебестоимость").
Такой подход обеспечивает прямое соединение таблиц двух независимых информационных баз в одном SQL-пакете с максимальной скоростью выполнения.