Как загрузить заголовок документа и табличную часть из внешней SQL-базы в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Метод 1. Использование внешних источников данных и механизма ИТОГИ ПО

Это наиболее современный и «нативный» способ для платформы 1С 8.3. Если внешняя база подключена через ВнешниеИсточникиДанных, мы можем обращаться к ней обычным запросом 1С. Рассмотрим ситуацию, когда у нас есть таблица заголовков и таблица строк, связанных по полю ID.

Основная проблема простого соединения (LEFT JOIN) заключается в том, что данные шапки дублируются для каждой строки табличной части. Чтобы не писать «тупые» алгоритмы сравнения ID текущей строки с предыдущей, воспользуемся предложением ИТОГИ ПО.

Разберем по шагам, как это работает в коде:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВнешнийЗаголовок.ID КАК Идентификатор,
    |   ВнешнийЗаголовок.Дата КАК ДатаДок,
    |   ВнешнийЗаголовок.Контрагент КАК КонтрагентНаименование,
    |   ВнешниеСтроки.Номенклатура КАК НоменклатураНаименование,
    |   ВнешниеСтроки.Количество КАК Количество,
    |   ВнешниеСтроки.Цена КАК Цена
    |ИЗ
    |   ВнешнийИсточникДанных.НашаБаза.ТаблицаЗаголовков КАК ВнешнийЗаголовок
    |       ЛЕВОЕ СОЕДИНЕНИЕ ВнешнийИсточникДанных.НашаБаза.ТаблицаСтрок КАК ВнешниеСтроки
    |       ПО ВнешнийЗаголовок.ID = ВнешниеСтроки.ID_Шапки
    |ИТОГИ ПО
    |   Идентификатор";

РезультатЗапроса = Запрос.Выполнить();
// Обходим сначала группировки (шапки документов)
ВыборкаШапка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаШапка.Следующий() Цикл
    
    // Здесь мы создаем документ 1С один раз для каждого ID из внешней базы
    НовыйДок = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
    НовыйДок.Дата = ВыборкаШапка.ДатаДок;
    // Заполняем остальные реквизиты шапки...
    
    // Теперь выбираем строки, относящиеся ТОЛЬКО к этой шапке
    ВыборкаСтрока = ВыборкаШапка.Выбрать();
    Пока ВыборкаСтрока.Следующий() Цикл
        // Проверяем, что строка ТЧ существует (на случай ЛЕВОГО СОЕДИНЕНИЯ без строк)
        Если ВыборкаСтрока.НоменклатураНаименование <> NULL Тогда
            НоваяСтрока = НовыйДок.Товары.Добавить();
            НоваяСтрока.Количество = ВыборкаСтрока.Количество;
            НоваяСтрока.Цена = ВыборкаСтрока.Цена;
            // Поиск номенклатуры по наименованию или коду...
        КонецЕсли;
    КонецЦикла;
    
    НовыйДок.Записать();
КонецЦикла;

Проанализируем этот подход: использование ОбходРезультатаЗапроса.ПоГруппировкам позволяет платформе самостоятельно управлять иерархией. Это избавляет от необходимости хранить переменные типа ПредыдущийID и делает код чистым и понятным.

Метод 2. Оптимизация через два раздельных запроса и индексацию в памяти

Если данных очень много (например, тысячи документов за один раз), LEFT JOIN может начать тормозить из-за избыточности передаваемого трафика (повторение данных шапки в каждой строке). В этом случае эффективнее применить стратегию разделения.

Рассмотрим алгоритм действий:

  1. Первым запросом выбираем только уникальные шапки документов за нужный период. Сохраняем их ID в массив.
  2. Вторым запросом выбираем все строки для всех шапок из массива: ГДЕ ID_Шапки В (&МассивID).
  3. Выгружаем строки во внутреннюю ТаблицаЗначений и индексируем её по полю ID_Шапки.

Посмотрим на пример реализации поиска строк:


// Предположим, ТЗ_Строки уже заполнена данными из SQL
ТЗ_Строки.Индексы.Добавить("ID_Шапки");

Для Каждого СтрокаШапки Из ТЗ_Шапки Цикл
    ДокОбъект = Документы.ЗаказПокупателя.СоздатьДокумент();
    // Заполнение шапки...
    
    // Быстрый поиск строк через НайтиСтроки по индексу
    ПараметрыОтбора = Новый Структура("ID_Шапки", СтрокаШапки.ID);
    НайденныеСтроки = ТЗ_Строки.НайтиСтроки(ПараметрыОтбора);
    
    Для Каждого СтрТЧ Из НайденныеСтроки Цикл
        НоваяСтрока = ДокОбъект.Товары.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрТЧ);
    КонецЦикла;
    
    ДокОбъект.Записать();
КонецЦикла;

Этот метод значительно быстрее на больших объемах, так как поиск в индексированной таблице значений происходит почти мгновенно, а объем данных, передаваемых по сети, минимален.

Метод 3. Использование JSON на стороне SQL (для современных систем)

Если вы работаете с MS SQL Server 2016 или новее, можно заставить SQL сформировать готовую иерархию. Выясним причину, почему это удобно: 1С отлично умеет парсить JSON в структуры и массивы.

На стороне SQL запрос будет выглядеть примерно так:

SELECT HeaderID, Date, (SELECT Item, Qty FROM Lines WHERE Lines.HID = Headers.ID FOR JSON PATH) as Items FROM Headers FOR JSON AUTO

В 1С мы получим одну длинную строку. Разберем, как ее обработать:


// Получаем JSONString через ADO или ВнешнийИсточник
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(JSONString);
ДанныеИзSQL = ПрочитатьJSON(ЧтениеJSON); // Получаем массив структур

Для Каждого ДокДанные Из ДанныеИзSQL Цикл
    НовыйДок = Документы.ПриходныйКассовыйОрдер.СоздатьДокумент();
    НовыйДок.СуммаДокумента = ДокДанные.TotalAmount;
    
    // ДокДанные.Items — это уже готовый массив строк
    Для Каждого СтрДанные Из ДокДанные.Items Цикл
        НоваяСтр = НовыйДок.МногострочнаяЧасть.Добавить();
        НоваяСтр.Количество = СтрДанные.Qty;
    КонецЦикла;
    
    НовыйДок.Записать();
КонецЦикла;

Метод 4. Синхронизация и контроль дублей

При любой загрузке крайне важно не создавать дубли при повторном запуске обработки. Рассмотрим стандартный механизм контроля. Нам понадобится РегистрСведений (например, «СоответствиеОбъектовДляОбмена»), где измерениями будут ИдентификаторSQL и ТипОбъекта, а ресурсом — Ссылка1С.

Проанализируем логику проверки:

  1. Перед созданием документа ищем ID_SQL в регистре.
  2. Если нашли — получаем ссылку и открываем существующий документ для обновления (ПолучитьОбъект()).
  3. Если не нашли — вызываем СоздатьДокумент() и после записи фиксируем связь в регистре.

Важный нюанс: при работе с типом uniqueidentifier в SQL помните, что порядок байтов в GUID в 1С и в MS SQL различается. Если вы ищете документ по GUID напрямую, используйте преобразование в строку на стороне SQL или специальные функции конвертации в 1С.

Метод 5. Использование ADO и GetRows()

Если вы используете старый метод подключения через COM-объект ADODB.Connection, забудьте про перебор записей через Recordset.MoveNext(). Это очень медленно из-за постоянных обращений между процессами.

Рассмотрим более производительный вариант:


Recordset = Connection.Execute(ТекстЗапроса);
// Выгружаем всё в двумерный массив одним вызовом
ДанныеМассивом = Recordset.GetRows();
Recordset.Close();

// Далее работаем с обычным массивом 1С, что в десятки раз быстрее
КоличествоЗаписей = ДанныеМассивом.GetUpperBound(1);
Для НомерЗаписи = 0 По КоличествоЗаписей Цикл
    ЗначениеПоля = ДанныеМассивом.GetValue(ИндексКолонки, НомерЗаписи);
    // Обработка...
КонецЦикла;

Подводя итог, выбор метода зависит от версии вашей платформы и объема данных. Для простых задач идеально подходит Внешний источник данных + ИТОГИ ПО, а для высоконагруженных систем — раздельные запросы с индексацией в памяти или JSON-трансформация на стороне сервера базы данных.

← На главную