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

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

При разработке отчетов и интеграций в 1С:Предприятии 7.7 с использованием внешней компоненты 1С++ и класса ODBCRecordset разработчики часто сталкиваются с задачей сопоставления данных текущей базы со сторонней базой данных SQL. В процессе решения этой задачи возникают типичные проблемы: некорректная работа метода УложитьСписокОбъектов() при передаче строк, ошибки парсера метаданных при кросс-базовых запросах и чтение периодических реквизитов. Разберем подробно причины этих проблем и пошаговые варианты их решения.

Почему метод УложитьСписокОбъектов() возвращает одну пустую строку

Проанализируем механику работы метода УложитьСписокОбъектов(). Данный метод предназначен исключительно для выгрузки ссылочных типов данных 1С:Предприятия 7.7 (элементы справочников, групп или документов). В СУБД метод формирует таблицу с фиксированной структурой колонок: val CHAR(9) (внутренний идентификатор объекта базы данных) и isfolder INT (признак группы).

Если в СписокЗначений поместить примитивные типы (например, строки с артикулами или числа), метод пытается преобразовать их во внутренний девятисимвольный ID объекта (id9). Так как строки не являются ссылками, метод не может получить идентификатор и записывает в результирующую таблицу одну служебную строку с пустым значением поля val и флагом isfolder = 2 (маркер отсутствия валидных ссылок).

Для передачи строковых списков (артикулов, кодов, наименований) во временную таблицу SQL существуют другие надежные подходы.

Способы передачи произвольных строковых данных во временную таблицу

Рассмотрим, как можно передать произвольный список артикулов в SQL-запрос:

  1. Использование метода ВыполнитьSQL_ИзТЗ(). Формируется стандартная ТаблицаЗначений, в которой создается колонка с нужной строковой длиной. Затем на стороне SQL создается структура временной таблицы, и данные переносятся пакетной вставкой:
    
    ТЗ = СоздатьОбъект("ТаблицаЗначений");
    ТЗ.НоваяКолонка("Артикул", "Строка", 50);
    // Заполняем ТЗ артикулами...
    
    рс = СоздатьОбъект("ODBCRecordset");
    рс.ВыполнитьИнструкцию("CREATE TABLE #ВТ_Артикулы (Артикул VARCHAR(50))");
    рс.ВыполнитьSQL_ИзТЗ(ТЗ, "#ВТ_Артикулы");
    
  2. Прямая вставка через многострочный INSERT. Создается временная таблица с префиксом # и заполняется порциями значений с помощью конструкции INSERT INTO #ВТ_Артикулы VALUES (...).
  3. Использование условия IN (...) в запросе. Если список содержит от нескольких десятков до сотен артикулов, его можно собрать в строку вида 'АРТ001', 'АРТ002' и подставить непосредственно в блок WHERE Таблица.Артикул IN (...).
  4. Связывание по ссылкам текущей базы. Если артикулы присутствуют в текущей информационной базе, в список значений помещаются сами элементы справочника Справочник.МПЗ. В этом случае метод рс.УложитьСписокОбъектов(СписокСсылок, "#ВТ_МПЗ", "МПЗ") отработает штатно, а в самом запросе выполняется соединение временной таблицы со справочником для получения артикулов.

Объединение двух баз данных в одном прямом SQL-запросе

Когда требуется соединить данные текущей базы и сторонней информационной базы (расположенной на том же SQL-сервере или на связанном сервере Linked Server), наиболее производительным решением является выполнение одного общего запроса с парсингом метаданных обеих конфигураций.

Разберем по шагам, как обработать метаданные двух разных баз с помощью объекта MetaDataWork:

  1. В тексте запроса таблицы и поля текущей базы обозначаются стандартным префиксом $.
  2. Таблицы и поля сторонней базы обозначаются специальным символом, например @, с указанием полного пути к базе данных СУБД (ИмяБазы2.dbo.@Справочник.МПЗ).
  3. Текст запроса сначала передается в глМД.ОбрМетаСКЛ() для разрешения метаданных текущей базы. Символы @ при этом парсером игнорируются.
  4. Создается второй объект MetaDataWork, к которому подключается файл метаданных сторонней базы: глМД2.ПрисоединитьМД(ПутьКБазе2 + "1Cv7.md").
  5. Символ @ в тексте запроса заменяется на $ с помощью СтрЗаменить(), после чего текст передается во второй парсер: глМД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.

Для корректной передачи параметров используем следующие правила:

  1. Для ссылочных значений (элементов справочников, документов, перечислений) получаем внутренний идентификатор базы данных с помощью метода глМД.ЗначениеВСтрокуБД(Элемент) и подставляем его строковое представление прямо в условие запроса.
  2. Даты в условиях преобразуются в формат 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Контрагент + "'
    |";
    
  3. Фильтрацию по объектам 1С (ссылкам) выполняем только для таблиц той информационной базы, в которой эти объекты созданы. Сопоставление с удаленной базой производим по общим примитивным полям (артикул, код, ИНН).

Получение периодических реквизитов из сторонней базы данных

Конструкция парсера $ПоследнееЗначение.Справочник.Реквизит(...) формирует подзапрос к таблице периодических значений _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-пакете с максимальной скоростью выполнения.

← На главную