Как быстро загрузить 267 000 записей из Excel в регистр сведений 1С и предотвратить зависание программы?

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

При работе с большими объемами данных, такими как 267 000 записей из Excel-файла, и попытке их загрузить в регистр сведений 1С, мы часто сталкиваемся с проблемой значительного замедления или даже полного зависания программы. Эта ситуация особенно актуальна, когда используемые подходы к работе с базой данных не оптимизированы для массовых операций. В этой статье мы подробно разберем, почему возникают такие проблемы, проанализируем типовые ошибки в коде и предложим эффективные методы для ускорения загрузки данных.

Наш регистр сведений, как выяснилось, состоит из трех измерений (два из которых являются ссылками на справочники, а одно — перечислением) и одного булевого ресурса. Именно такая структура, вкупе с неоптимизированным кодом, может привести к катастрофическим последствиям при загрузке сотен тысяч записей. Рассмотрим, как избежать этих проблем.

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

Основная причина медленной работы при загрузке большого объема данных кроется в неэффективном взаимодействии с базой данных внутри цикла обработки каждой строки. Давайте проанализируем типовой проблемный код, представленный в обсуждении:


// ... код чтения Excel ...

Для строкаТП = 0 По всегоСтрок Цикл 
    Если ПустаяСтрока(СокрЛП(МассивКолонок.Получить(1).Получить(строкаТП))) тогда
        // Сообщить("Строка пустая "+ строкаТП); // <-- Проблемное место 1
        Продолжить;
    Иначе
        НаименованиеРеквизита1 = МассивКолонок.Получить(0).Получить(строкаТП);
        НаименованиеРеквизита2 = МассивКолонок.Получить(1).Получить(строкаТП);
        НаименованиеРеквизита3 = МассивКолонок.Получить(2).Получить(строкаТП);
        НаименованиеРеквизита3=СтрЗаменить(КонтрагентНаименование,"""","");
        НаименованиеРеквизита4 = Булево(МассивКолонок.Получить(3).Получить(строкаТП)); 
        
        // Поиск перечисления в цикле для каждой строки - Проблемное место 2
        Для каждого ЭлементПеречисления Из Метаданные.Перечисления.нв_ВидыПечати.ЗначенияПеречисления Цикл
            Если ЭлементПеречисления.Синоним = ВидПечатиНаименование Тогда
                СсылкаНаПеречисление = Перечисления.нв_ВидыПечати[ЭлементПеречисления.Имя];
                Прервать;
            КонецЕсли; 
        КонецЦикла;    
        
        ////Тут ниже запрос вытаскивает нужные данные по "НаименованиеРеквизита2", и сообщает, которые данные не нашел // <-- Проблемное место 3
        ------------
        ////Далее идет второй запрос, который вытаскивает нужные данные по "НаименованиеРеквизита3", которые данные не нашел, сообщает // <-- Проблемное место 3
        ---------------
        мНаборЗаписей = РегистрыСведений.МойРегистрСведений.СоздатьНаборЗаписей();
        // прочитали то, что есть в базе // <-- Проблемное место 4
        мНаборЗаписей.Прочитать();
        // заполняем свойства
        НоваяЗапись = мНаборЗаписей.Добавить();
        НоваяЗапись.Измерение1 = СсылкаНаПеречисление;
        НоваяЗапись.Измерение2 = ФизическоеЛицо;
        НоваяЗапись.Измерение3 = Контрагент;
        НоваяЗапись.Ресурс1 = Напечатан;
        мНаборЗаписей.Записать(); // <-- Проблемное место 4
    КонецЕсли;        
КонецЦикла;

Давайте разберем основные "проблемные места" в этом коде:

  1. Чрезмерное использование Сообщить() в цикле. Функция Сообщить() предназначена для информирования пользователя о некритичных событиях. При вызове ее для каждой из 267 000 записей, клиентское приложение пытается отобразить каждое сообщение, что приводит к заполнению буфера сообщений, огромной нагрузке на интерфейс и, как следствие, зависанию программы. Для массовых операций подобные уведомления следует либо полностью исключить, либо направлять в файл лога.
  2. Поиск перечислений и справочников внутри цикла для каждой строки. Код ищет ссылку на элемент перечисления, перебирая все значения перечисления в цикле для каждой обрабатываемой строки. Аналогично, если поиск ссылок на справочники осуществляется индивидуальными запросами или перебором по наименованию в цикле, это вызывает огромное количество обращений к базе данных (или к метаданным в случае перечислений).
  3. Индивидуальные запросы к базе данных для каждой строки. Если для получения ссылочных данных (измерений ФизическоеЛицо и Контрагент) используются отдельные запросы внутри цикла, то для 267 000 записей будет выполнено минимум 534 000 запросов к базе данных (по два на каждую строку). Каждый запрос имеет свои накладные расходы на выполнение, что делает этот подход крайне неэффективным.
  4. Построчное чтение и запись всего набора записей регистра сведений (мНаборЗаписей.Прочитать() и мНаборЗаписей.Записать() в цикле). Это критическая ошибка производительности. В каждой итерации цикла происходит следующее:
    • мНаборЗаписей = РегистрыСведений.МойРегистрСведений.СоздатьНаборЗаписей(); — создается пустой менеджер набора записей.
    • мНаборЗаписей.Прочитать(); — из базы данных полностью считывается весь текущий набор записей регистра. Если регистр уже содержит десятки или сотни тысяч записей, это операция будет очень медленной.
    • НоваяЗапись = мНаборЗаписей.Добавить(); — добавляется одна новая запись в уже считанный набор.
    • мНаборЗаписей.Записать(); — весь набор записей (старые + одна новая) полностью перезаписывается в базу данных.

    Таким образом, для 267 000 записей регистр будет 267 000 раз полностью прочитан и 267 000 раз полностью перезаписан. Это создает колоссальную нагрузку на СУБД, вызывает длительные блокировки и неминуемо приводит к зависанию.

Оптимизация загрузки данных: Массовая запись и предварительная подготовка

Для эффективной загрузки большого объема данных из Excel в регистр сведений мы должны перейти от поэлементной обработки и взаимодействия с базой данных к массовым операциям. Ключевой принцип — минимизировать количество обращений к СУБД и выполнять максимально возможный объем работы за одно действие.

  1. Эффективное чтение Excel-файла

    Изначально файл Excel читается с использованием COM-объекта Excel.Application. Хотя этот метод работает, он имеет свои недостатки: требует установленного Microsoft Office, может зависать, и его выполнение на сервере не всегда оптимально. Более предпочтительным и универсальным способом является использование встроенного в 1С объекта ТабличныйДокумент.

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

    Создадим функцию для чтения данных из файла Excel:

    
    // Функция чтения данных из файла Excel
    Функция ПрочитатьДанныеИзExcel(ПутьКФайлу)
        Перем ТаблицаДанных;
    
        Попытка
            ТаблицаДокумент = Новый ТабличныйДокумент;
            ТаблицаДокумент.Прочитать(ПутьКФайлу);
            
            // Выгружаем данные с первого листа. 
            // Если Excel-файл содержит заголовки, можно указать ОбластьЯчеек
            ТаблицаДанных = ТаблицаДокумент.Выгрузить(ТаблицаДокумент.Область(2, 1, ТаблицаДокумент.ВысотаТаблицы, ТаблицаДокумент.ШиринаТаблицы));
            
        Исключение
            ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Ошибка, , , "Ошибка при чтении файла Excel: " + ОписаниеОшибки());
            Возврат Неопределено;
        КонецПопытки;
    
        Возврат ТаблицаДанных;
    КонецФункции
    
    // В основной обработке вызываем так:
    // ТаблицаИзExcel = ПрочитатьДанныеИзExcel(ПутьКФайлу);
    // Если ТаблицаИзExcel = Неопределено Тогда
    //    Возврат;
    // КонецЕсли;
    //
    // Если ТаблицаИзExcel.Колонки.Количество() < 4 Тогда
    //    ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Ошибка, , , "В файле Excel недостаточно колонок.");
    //    Возврат;
    // КонецЕсли;
    //
    // // Добавляем колонки в ТЗ для хранения ссылок, которые будем находить
    // ТаблицаИзExcel.Колонки.Добавить("СсылкаИзмерение1", Новый ОписаниеТипов("ПеречислениеСсылка.нв_ВидыПечати"));
    // ТаблицаИзExcel.Колонки.Добавить("СсылкаИзмерение2", Новый ОписаниеТипов("СправочникСсылка.ФизическиеЛица")); // Пример
    // ТаблицаИзExcel.Колонки.Добавить("СсылкаИзмерение3", Новый ОписаниеТипов("СправочникСсылка.Контрагенты")); // Пример
    

    Обратите внимание, что мы добавили в полученную ТаблицуЗначений новые колонки для ссылок, чтобы хранить в них найденные значения, не модифицируя исходные данные из Excel.

  2. Подготовка данных и предварительная загрузка ссылок

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

    1. Кэширование перечислений: Поскольку значения перечислений фиксированы, их можно прочитать один раз и сохранить в соответствии для быстрого доступа.
    2. 
      // В начале процедуры загрузки, один раз:
      СоответствиеВидовПечати = Новый Соответствие;
      Для каждого ЭлементПеречисления Из Метаданные.Перечисления.нв_ВидыПечати.ЗначенияПеречисления Цикл
          СоответствиеВидовПечати.Вставить(ЭлементПеречисления.Синоним, Перечисления.нв_ВидыПечати[ЭлементПеречисления.Имя]);
      КонецЦикла;
      
    3. Массовый поиск ссылок на справочники:

      Предположим, у нас есть наименования справочников в колонках "НаименованиеРеквизита2" и "НаименованиеРеквизита3" из Excel. Мы можем собрать все уникальные наименования, а затем одним запросом получить соответствующие ссылки.

      
      // Собираем уникальные наименования для измерения 2 (например, ФизическиеЛица)
      УникальныеНаименования2 = Новый Массив;
      Для каждого СтрокаТЗ Из ТаблицаИзExcel Цикл
          Если Не УникальныеНаименования2.Найти(СтрокаТЗ.КолонкаСНаименованием2) Тогда
              УникальныеНаименования2.Добавить(СтрокаТЗ.КолонкаСНаименованием2);
          КонецЕсли;
      КонецЦикла;
      
      // Аналогично для измерения 3 (например, Контрагенты)
      УникальныеНаименования3 = Новый Массив;
      Для каждого СтрокаТЗ Из ТаблицаИзExcel Цикл
          Если Не УникальныеНаименования3.Найти(СтрокаТЗ.КолонкаСНаименованием3) Тогда
              УникальныеНаименования3.Добавить(СтрокаТЗ.КолонкаСНаименованием3);
          КонецЕсли;
      КонецЦикла;
      
      // Выполняем массовый запрос для получения ссылок (измерение 2)
      Запрос = Новый Запрос;
      Запрос.Текст = 
          "ВЫБРАТЬ
          |    СправочникФизическиеЛица.Ссылка КАК Ссылка,
          |    СправочникФизическиеЛица.Наименование КАК Наименование
          |ИЗ
          |    Справочник.ФизическиеЛица КАК СправочникФизическиеЛица
          |ГДЕ
          |    СправочникФизическиеЛица.Наименование В (&СписокНаименований)";
      Запрос.УстановитьПараметр("СписокНаименований", УникальныеНаименования2);
      РезультатЗапроса2 = Запрос.Выполнить().Выгрузить();
      
      // Выполняем массовый запрос для получения ссылок (измерение 3)
      Запрос.Текст = 
          "ВЫБРАТЬ
          |    СправочникКонтрагенты.Ссылка КАК Ссылка,
          |    СправочникКонтрагенты.Наименование КАК Наименование
          |ИЗ
          |    Справочник.Контрагенты КАК СправочникКонтрагенты
          |ГДЕ
          |    СправочникКонтрагенты.Наименование В (&СписокНаименований)";
      Запрос.УстановитьПараметр("СписокНаименований", УникальныеНаименования3);
      РезультатЗапроса3 = Запрос.Выполнить().Выгрузить();
      
      // Теперь сопоставляем ссылки с нашей таблицей из Excel
      Для каждого СтрокаТЗ Из ТаблицаИзExcel Цикл
          // Перечисление
          Если СоответствиеВидовПечати.ЕстьКлюч(СтрокаТЗ.КолонкаСНаименованием1) Тогда
              СтрокаТЗ.СсылкаИзмерение1 = СоответствиеВидовПечати.Получить(СтрокаТЗ.КолонкаСНаименованием1);
          Иначе
              // Обработка случая, когда перечисление не найдено
              ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Ошибка, , , "Не найдено значение перечисления '" + СтрокаТЗ.КолонкаСНаименованием1 + "' в строке " + СтрокаТЗ.НомерСтроки);
          КонецЕсли;
      
          // Справочник 2
          НайденнаяСтрока2 = РезультатЗапроса2.Найти(СтрокаТЗ.КолонкаСНаименованием2, "Наименование");
          Если НайденнаяСтрока2 <> Неопределено Тогда
              СтрокаТЗ.СсылкаИзмерение2 = НайденнаяСтрока2.Ссылка;
          Иначе
              // Обработка случая, когда справочник не найден
              ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Ошибка, , , "Не найден справочник ФизическиеЛица '" + СтрокаТЗ.КолонкаСНаименованием2 + "' в строке " + СтрокаТЗ.НомерСтроки);
          КонецЕсли;
      
          // Справочник 3
          НайденнаяСтрока3 = РезультатЗапроса3.Найти(СтрокаТЗ.КолонкаСНаименованием3, "Наименование");
          Если НайденнаяСтрока3 <> Неопределено Тогда
              СтрокаТЗ.СсылкаИзмерение3 = НайденнаяСтрока3.Ссылка;
          Иначе
              // Обработка случая, когда справочник не найден
              ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Ошибка, , , "Не найден справочник Контрагенты '" + СтрокаТЗ.КолонкаСНаименованием3 + "' в строке " + СтрокаТЗ.НомерСтроки);
          КонецЕсли;
      КонецЦикла;
      
    4. Фильтрация и подготовка к записи: После сопоставления ссылок, мы должны отфильтровать строки, для которых не удалось найти ссылки, или создать для них новые элементы справочников (если это предусмотрено логикой). Для записи в регистр сведений нам нужна ТаблицаЗначений, колонки которой соответствуют измерениям и ресурсам регистра.
    5. 
      // Создаем ТаблицуЗначений, соответствующую структуре регистра сведений
      ТЗДляЗаписи = Новый ТаблицаЗначений;
      ТЗДляЗаписи.Колонки.Добавить("Измерение1", Новый ОписаниеТипов("ПеречислениеСсылка.нв_ВидыПечати"));
      ТЗДляЗаписи.Колонки.Добавить("Измерение2", Новый ОписаниеТипов("СправочникСсылка.ФизическиеЛица"));
      ТЗДляЗаписи.Колонки.Добавить("Измерение3", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
      ТЗДляЗаписи.Колонки.Добавить("Ресурс1", Новый ОписаниеТипов("Булево"));
      
      Для каждого СтрокаТЗ Из ТаблицаИзExcel Цикл
          // Проверяем, что все ссылки найдены
          Если СтрокаТЗ.СсылкаИзмере1 <> Неопределено 
              И СтрокаТЗ.СсылкаИзмерение2 <> Неопределено 
              И СтрокаТЗ.СсылкаИзмерение3 <> Неопределено Тогда
      
              НоваяСтрока = ТЗДляЗаписи.Добавить();
              НоваяСтрока.Измерение1 = СтрокаТЗ.СсылкаИзмерение1;
              НоваяСтрова.Измерение2 = СтрокаТЗ.СсылкаИзмерение2;
              НоваяСтрока.Измерение3 = СтрокаТЗ.СсылкаИзмерение3;
              НоваяСтрока.Ресурс1 = СтрокаТЗ.КолонкаСБулевоЗначением; // Или НаименованиеРеквизита4
          КонецЕсли;
      КонецЦикла;
      

    Если необходимо обновлять существующие записи, а не только добавлять новые, то перед записью можно использовать запрос для получения существующих записей регистра, объединить их с подготовленной ТЗ и создать на основе этого объединенного результата окончательную ТЗ для записи. Или же предварительно очистить регистр, если это допустимо.

  3. Массовая запись в регистр сведений

    После того как мы подготовили ТаблицуЗначений со всеми необходимыми данными и ссылками, мы можем использовать метод НаборЗаписей.Загрузить(ТаблицаЗначений) для массовой записи данных. Этот метод значительно более производителен, так как он выполняет все операции с базой данных за один или несколько транзакционных блоков, а не по одной записи.

    
    // Создаем набор записей регистра
    мНаборЗаписей = РегистрыСведений.МойРегистрСведений.СоздатьНаборЗаписей();
    
    // Очищаем существующие записи, если это требуется (например, если загружаем данные "с нуля" или обновляем весь регистр)
    // Если очистка всего регистра не нужна, и требуется лишь обновление или добавление,
    // то этот шаг следует пропустить, или выполнить запрос на удаление только тех записей,
    // которые будут перезаписаны, или объединить данные в ТЗ.
    // Если регистр периодический и необходимо перезаписать записи за определенный период,
    // то можно установить отбор по периоду перед Прочитать()/Записать().
    // В данном случае, когда мы хотим просто заменить/добавить данные, если регистр не периодический
    // и подразумевается полная перезапись всего набора, то перед загрузкой можно его очистить.
    
    // Если нужно добавить новые записи и/или обновить существующие,
    // и регистр не периодический (или отбор по измерениям уникален):
    // мНаборЗаписей.Прочитать(); // Прочитываем текущий набор, если хотим сохранить существующие записи и добавить новые
    // Далее нужно логику объединения ТЗ_ДляЗаписи с мНаборЗаписей, чтобы обновить существующие записи
    // и добавить новые. Это сложнее, чем просто Загрузить.
    
    // В простейшем случае, если мы перезаписываем все данные регистра,
    // то сначала его можно очистить, если это допустимо:
    // мНаборЗаписей.Отбор.Установить(Новый Структура()); // Устанавливаем отбор по всем измерениям, чтобы очистить все
    // мНаборЗаписей.Записать(); // Запись пустого набора = очистка регистра
    
    // Если мы хотим именно заменить все данные:
    мНаборЗаписей.Очистить(); // Удалит все записи в регистре, если не установлен отбор
                              // Если регистр имеет измерения, по которым может быть отбор, 
                              // например, по ФизическомуЛицу, и мы хотим обновить данные
                              // только для конкретного ФизическогоЛица, то нужно установить отбор:
                              // мНаборЗаписей.Отбор.Измерение2.Установить(СсылкаНаФизическоеЛицо);
    
    // Загружаем подготовленную ТаблицуЗначений в набор записей
    мНаборЗаписей.Загрузить(ТЗДляЗаписи);
    
    // Записываем измененный набор в базу данных
    мНаборЗаписей.Записать();
    

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

  4. Обработка ошибок и логирование

    Как мы уже выяснили, использование Сообщить() в цикле для большого количества записей крайне неэффективно. Вместо этого, для отслеживания прогресса и логирования ошибок мы рекомендуем:

    • Запись в журнал регистрации: Для критичных ошибок или важных событий используем ЗаписьЖурналаРегистрации().
    • Запись в текстовый файл: Создадим отдельный текстовый файл для подробного лога процесса загрузки и всех найденных несоответствий или ошибок.
    • Обновление строки состояния: Для информирования пользователя о ходе выполнения (например, "Обработано X из Y записей") используем УстановитьТекстСостояния().
    
    // Пример использования текстового файла для лога
    ИмяФайлаЛога = "ЗагрузкаРС_" + Формат(ТекущаяДата(), "ДФ=yyyyMMdd_HHmmss") + ".log";
    ПолныйПутьКФайлуЛога = КаталогВременныхФайлов() + ИмяФайлаЛога;
    ТекстовыйДокументЛога = Новый ТекстовыйДокумент;
    
    // ... внутри цикла обработки строк Excel ...
    // вместо Сообщить("Строка пустая " + строкаТП);
    ТекстовыйДокументЛога.ДобавитьСтроку("Строка пустая: " + строкаТП);
    
    // ... если справочник не найден ...
    ТекстовыйДокументЛога.ДобавитьСтроку("Ошибка: Не найден справочник '" + СтрокаТЗ.КолонкаСНаименованием2 + "' в строке " + СтрокаТЗ.НомерСтроки);
    
    // ... после цикла ...
    ТекстовыйДокументЛога.Записать(ПолныйПутьКФайлуЛога);
    ЗаписьЖурналаРегистрации("ЗагрузкаИзExcel", УровеньЖурналаРегистрации.Информация, , , "Загрузка завершена. Лог в файле: " + ПолныйПутьКФайлуЛога);
    
  5. Оптимизация удаления записей

    Мы столкнулись с проблемой, что даже удаление 18 000 записей занимает 15 минут, что указывает на аналогичные ошибки в подходе к удалению. Если удаление выполняется в цикле по одной записи, это так же неэффективно. Для массового удаления необходимо использовать либо очистку всего набора записей (мНаборЗаписей.Очистить(); и мНаборЗаписей.Записать();), либо запрос, который удаляет записи по определенным условиям.

    
    // Удаление всех записей регистра (если это допустимо)
    МенеджерЗаписей = РегистрыСведений.МойРегистрСведений;
    НаборДляОчистки = МенеджерЗаписей.СоздатьНаборЗаписей();
    НаборДляОчистки.Очистить();
    НаборДляОчистки.Записать();
    
    // Или удаление записей по определенным условиям с помощью запроса
    // (требует прав на интерактивное удаление объектов или выполнение через привилегированный режим)
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "УДАЛИТЬ ИЗ
        |    РегистрСведений.МойРегистрСведений КАК РегистрСведенийМойРегистрСведений
        |ГДЕ
        |    РегистрСведенийМойРегистрСведений.Измерение2 В (&СписокФизическихЛиц)"; // Пример условия
    Запрос.УстановитьПараметр("СписокФизическихЛиц", МассивСсылокНаФизическиеЛицаДляУдаления);
    Запрос.Выполнить();
    

    Важно помнить, что удаление через запрос не вызывает обработчики событий объекта, поэтому этот метод следует использовать с осторожностью.

Альтернативные и более мощные методы переноса данных

Важно отметить, что если вашей изначальной задачей было перемещение записей из одного регистра сведений одной базы данных в другую базу данных, существуют более подходящие и производительные инструменты, чем выгрузка в Excel и последующая загрузка. Рассмотрим их подробнее.

  1. Универсальный обмен данными в формате XML (Конвертация Данных 2.0/3.0)

    Это самый мощный и рекомендуемый способ для переноса данных между базами 1С, особенно если конфигурации одинаковые или очень похожие. Конвертация Данных (КД) позволяет создавать правила обмена, которые описывают, как объекты одной конфигурации соотносятся с объектами другой.

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

    1. Выгрузка описания конфигураций: Используем обработку MD8xExp.epf (или аналогичные средства) для выгрузки описаний структуры обеих баз данных (источника и приемника).
    2. Настройка правил обмена в КД: В конфигурации "Конвертация Данных" (доступной на ИТС) загружаем эти описания. Затем создаем новый обмен, указывая конфигурации источника и приемника. Внутри этого обмена мы определяем правила для нашего регистра сведений: какие измерения и ресурсы регистра источника соответствуют измерениям и ресурсам регистра приемника. КД также позволяет автоматически создавать правила для связанных объектов (например, для справочников, на которые ссылаются измерения регистра), что критически важно для корректного переноса ссылочных данных.
    3. Выгрузка данных: Используем типовую обработку "Универсальный обмен данными в формате XML" в базе-источнике. Загружаем туда созданные правила обмена и указываем, какие данные (например, записи нашего регистра сведений) нужно выгрузить. Обработка сформирует XML-файл.
    4. Загрузка данных: Используем ту же обработку "Универсальный обмен данными в формате XML" в базе-приемнике. Загружаем XML-файл, и обработка автоматически создаст или обновит соответствующие записи регистра сведений и связанные с ними объекты, разрешая ссылки.

    Преимущества: Высокая производительность, надежность, автоматическое разрешение ссылок, возможность трансформации данных в процессе переноса, поддержка больших объемов данных.

  2. JSON/XML для программного обмена данными

    Если требуется полностью программный обмен данными, например, через HTTP-сервисы, или если вы хотите создать собственный механизм без использования КД, форматы JSON или XML являются отличным выбором. 1С имеет встроенные функции для работы с этими форматами (ЧтениеJSON, ЗаписьJSON, ЧтениеXML, ЗаписьXML).

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

    1. Выгрузка в JSON/XML: В базе-источнике формируем ТаблицуЗначений или массив структур с данными регистра, а затем сериализуем ее в JSON или XML строку/файл.
    2. 
      // Пример выгрузки в JSON
      Запрос = Новый Запрос;
      Запрос.Текст = "ВЫБРАТЬ
                     |    МойРегистрСведений.Измерение1 КАК Измерение1,
                     |    МойРегистрСведений.Измерение2 КАК Измерение2,
                     |    МойРегистрСведений.Измерение3 КАК Измерение3,
                     |    МойРегистрСведений.Ресурс1 КАК Ресурс1
                     |ИЗ
                     |    РегистрСведений.МойРегистрСведений КАК МойРегистрСведений";
      РезультатЗапроса = Запрос.Выполнить().Выгрузить();
      
      СтрокаJSON = ЗаписатьJSON(РезультатЗапроса);
      // Далее сохраняем СтрокаJSON в файл или передаем по сети
      
    3. Загрузка из JSON/XML: В базе-приемнике читаем JSON/XML файл или строку, десериализуем ее в ТаблицуЗначений или массив структур, а затем используем описанный выше подход с массовой записью через НаборЗаписей.Загрузить().
    4. 
      // Пример загрузки из JSON
      // Предположим, у нас есть СтрокаJSON с данными регистра
      ЧтениеJSON = Новый ЧтениеJSON;
      ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
      ДанныеИзJSON = ЧтениеJSON.Прочитать();
      ЧтениеJSON.Закрыть();
      
      // ДанныеИзJSON теперь представляет собой ТаблицуЗначений или Массив
      // Далее используем подготовку данных и НаборЗаписей.Загрузить(ДанныеИзJSON)
      

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

Использование временных таблиц и запросов для обработки и объединения данных

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

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

  1. Загрузка данных из Excel во временную ТаблицуЗначений: Мы уже знаем, как это сделать с помощью ТабличныйДокумент.
  2. Создание временной таблицы в запросе: Передаем ТаблицуЗначений, полученную из Excel, в запрос в качестве параметра типа ТаблицаЗначений. В тексте запроса эта таблица может быть использована как временная.
  3. Объединение и сопоставление данных: Используем SQL-операторы СОЕДИНЕНИЕ (ЛЕВОЕ, ВНУТРЕННЕЕ и т.д.) для сопоставления данных из временной таблицы с данными из таблиц справочников, документов или регистров сведений в базе данных.
  4. 
    // Пример использования запроса для получения ссылок и подготовки данных для регистра
    // Предположим, ТаблицаИзExcel уже содержит колонки с наименованиями и булевым значением.
    // Необходимо добавить в нее колонки для ссылок, как это было описано ранее.
    // Здесь мы используем ее как источник данных для временной таблицы.
    
    // Создаем менеджер временных таблиц
    МВТ = Новый МенеджерВременныхТаблиц;
    
    // Помещаем данные из Excel во временную таблицу
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МВТ;
    Запрос.Текст = 
        "ПОМЕСТИТЬ ВТ_ДанныеExcel
        |ИЗ
        |    &ТаблицаИзExcel КАК ТаблицаИзExcel
        |;
        |
        |ВЫБРАТЬ
        |    ТаблицаИзExcel.КолонкаСНаименованием1 КАК НаименованиеПеречисления,
        |    ТаблицаИзExcel.КолонкаСНаименованием2 КАК НаименованиеФизЛица,
        |    ТаблицаИзExcel.КолонкаСНаименованием3 КАК НаименованиеКонтрагента,
        |    ТаблицаИзExcel.КолонкаСБулевоЗначением КАК БулевоРесурс
        |ИЗ
        |    ВТ_ДанныеExcel КАК ТаблицаИзExcel";
    Запрос.УстановитьПараметр("ТаблицаИзExcel", ТаблицаИзExcel); // Передаем нашу ТЗ как параметр
    Запрос.Выполнить();
    
    // Теперь основной запрос, который сопоставляет данные и получает ссылки
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МВТ;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Перечисления.нв_ВидыПечати.Ссылка КАК Измерение1,
        |    СправочникФизическиеЛица.Ссылка КАК Измерение2,
        |    СправочникКонтрагенты.Ссылка КАК Измерение3,
        |    ВТ_ДанныеExcel.БулевоРесурс КАК Ресурс1
        |ИЗ
        |    ВТ_ДанныеExcel КАК ВТ_ДанныеExcel
        |        ЛЕВОЕ СОЕДИНЕНИЕ Перечисления.нв_ВидыПечати КАК Перечисления.нв_ВидыПечати
        |        ПО ВТ_ДанныеExcel.НаименованиеПеречисления = Перечисления.нв_ВидыПечати.Синоним
        |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ФизическиеЛица КАК СправочникФизическиеЛица
        |        ПО ВТ_ДанныеExcel.НаименованиеФизЛица = СправочникФизическиеЛица.Наименование
        |        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК СправочникКонтрагенты
        |        ПО ВТ_ДанныеExcel.НаименованиеКонтрагента = СправочникКонтрагенты.Наименование
        |ГДЕ
        |    Перечисления.нв_ВидыПечати.Ссылка ЕСТЬ НЕ NULL
        |    И СправочникФизическиеЛица.Ссылка ЕСТЬ НЕ NULL
        |    И СправочникКонтрагенты.Ссылка ЕСТЬ НЕ NULL"; // Отбираем только те, для которых найдены все ссылки
        
    ТЗДляЗаписиВРегистр = Запрос.Выполнить().Выгрузить();
    
    // Далее используем ТЗДляЗаписиВРегистр с мНаборЗаписей.Загрузить()
    // ...
    

Обращаем ваше внимание на необходимость соответствия типов данных в колонках ТаблицыЗначений, передаваемой в запрос, и полей временной таблицы. Если типы не совпадают, могут возникнуть ошибки или замедление производительности.

Применяя эти оптимизированные подходы, вы сможете значительно сократить время загрузки данных из Excel в регистр сведений, предотвратить зависание программы и обеспечить стабильную работу вашей системы 1С даже с очень большими объемами информации.

← На главную