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

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

При импорте данных из внешних источников (например, при загрузке информации из файлов Excel) разработчики часто сталкиваются с ситуацией, когда в одной ячейке таблицы через разделитель записано несколько значений. Типичный пример — список штрихкодов номенклатуры, разделенный точкой с запятой или двоеточием. Для корректной обработки таких данных в системе 1С необходимо решить две важные задачи: правильно разбить исходную строку на массив подстрок и максимально быстро записать изменения в базу данных без создания избыточной нагрузки на СУБД.

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

Способ 1. Использование встроенной платформенной функции СтрРазделить

Начиная с версии платформы 1С:Предприятие 8.3.6, разработчикам доступна встроенная функция СтрРазделить. Данный метод работает на уровне ядра платформы (на C++), что обеспечивает максимальную скорость выполнения при обработке больших текстовых массивов.

Давайте рассмотрим синтаксис этой функции:

Результат = СтрРазделить(Строка, Разделитель, ВключатьПустые)

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


// Исходная строка содержит разные разделители (двоеточие и точку с запятой)
СтрокаШтрихкодов = "123456789;987654321:555555555";

// Передаем строку разделителей ";:"
МассивШтрихкодов = СтрРазделить(СтрокаШтрихкодов, ";:", Ложь);

// В результате мы получим массив из трех элементов:
// МассивШтрихкодов[0] = "123456789"
// МассивШтрихкодов[1] = "987654321"
// МассивШтрихкодов[2] = "555555555"

Параметр ВключатьПустые со значением Ложь автоматически отсекает пустые строки, которые могли возникнуть из-за случайных двойных разделителей в исходном файле Excel. Это избавляет нас от необходимости писать дополнительные проверки в коде.

Способ 2. Использование функции библиотеки стандартных подсистем (БСП)

Если разработка ведется на более старых версиях платформы или требуется разобрать строку строго по конкретной многосимвольной фразе (например, разделить текст по слову " и "), стандартный метод СтрРазделить нам не подойдет. В этом случае мы должны использовать функцию из общего модуля БСП:

СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(Значение, Разделитель, ПропускатьПустыеСтроки)

Давайте выясним причину разного поведения этих функций. Функция БСП воспринимает параметр Разделитель строго как единую неделимую последовательность символов. Проанализируем код:


// Пример использования функции БСП
СтрокаШтрихкодов = "Ковер_1 и Ковер_2 и Ковер_3";
Разделитель = " и ";

МассивИмен = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(СтрокаШтрихкодов, Разделитель, Истина);

// Получим массив:
// МассивИмен[0] = "Ковер_1"
// МассивИмен[1] = "Ковер_2"
// МассивИмен[2] = "Ковер_3"

Обратите внимание на логику булевого флага для пустых строк: у БСП параметр называется ПропускатьПустыеСтроки (значение Истина означает исключение пустых элементов), в то время как у платформенной функции СтрРазделить параметр называется ВключатьПустые (для исключения пустых элементов передается Ложь).

Анализ проблемы производительности при работе с СУБД (Проблема N+1)

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


// НЕПРАВИЛЬНЫЙ ВАРИАНТ (Низкая производительность)
Для Шаг = 0 По МассивШтрихкодов.Количество() - 1 Цикл
    // Поиск в цикле порождает отдельный запрос к СУБД на каждой итерации
    ШтрихкодСсылка = Справочники.ШтрихКодыКовров.НайтиПоКоду(МассивШтрихкодов[Шаг]);
    
    Если ЗначениеЗаполнено(ШтрихкодСсылка) Тогда
        ШтрихкодОбъект = ШтрихкодСсылка.ПолучитьОбъект();
        ШтрихкодОбъект.Оприходован = Истина;
        ШтрихкодОбъект.Записать(); // Запись в цикле без явной транзакции
    КонецЕсли;
КонецЦикла;

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

  1. Проблема N+1 запросов: Метод НайтиПоКоду под капотом выполняет полноценный SQL-запрос к СУБД. Если в массиве содержится 1000 штрихкодов, система сделает 1000 отдельных запросов к базе данных, что приведет к колоссальным временным задержкам на сетевое взаимодействие.
  2. Неявные транзакции на каждую запись: При вызове метода Записать() платформа 1С автоматически открывает транзакцию, фиксирует изменения одного объекта и закрывает транзакцию. Запись 1000 элементов по отдельности заставит СУБД выполнить 1000 транзакций, нагружая жесткий диск сервера транзакционными логами.
  3. Холостые перезаписи: Если объект уже имел признак Оприходован = Истина, система все равно получит объект, перезапишет его и вызовет все обработчики событий, тратя ресурсы сервера впустую.

Разработка оптимизированного алгоритма с использованием запросов и пакетных транзакций

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

Посмотрим на пример оптимизированного кода:


// 1. Получаем массив уникальных штрихкодов (исключаем дубли)
МассивШтрихкодов = СтрРазделить(ИсходнаяСтрока, ";", Ложь);

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

Запрос.УстановитьПараметр("МассивШтрихкодов", МассивШтрихкодов);
РезультатЗапроса = Запрос.Выполнить();

Если Не РезультатЗапроса.Пустой() Тогда
    
    Выборка = РезультатЗапроса.Выбрать();
    
    // 3. Открываем явную транзакцию для ускорения записи порциями
    НачатьТранзакцию();
    СчетчикЗаписей = 0;
    
    Пока Выборка.Следующий() Цикл
        
        // Получаем объект для изменения
        ШтрихкодОбъект = Выборка.Ссылка.ПолучитьОбъект();
        ШтрихкодОбъект.Оприходован = Истина;
        
        // Важный момент: отключаем выполнение тяжелого прикладного кода в модуле объекта,
        // так как мы выполняем групповую техническую операцию
        ШтрихкодОбъект.ОбменДанными.Загрузка = Истина;
        
        Попытка
            ШтрихкодОбъект.Записать();
            СчетчикЗаписей = СчетчикЗаписей + 1;
            
            // Каждые 500 записей фиксируем транзакцию и открываем новую,
            // чтобы избежать длительных блокировок таблиц СУБД для других пользователей
            Если СчетчикЗаписей % 500 = 0 Тогда
                ЗафиксироватьТранзакцию();
                НачатьТранзакцию();
            КонецЕсли;
        Исключение
            // В случае ошибки отменяем текущую транзакцию и пишем лог
            ОтменитьТранзакцию();
            ЗаписьЖурналаРегистрации("ГрупповоеОприходование",
                УровеньЖурналаРегистрации.Ошибка, , ,
                "Ошибка при записи объекта: " + ОписаниеОшибки());
            ВызватьИсключение "Не удалось записать изменения. Операция отменена.";
        КонецПопытки;
        
    КонецЦикла;
    
    // Закрываем финальную транзакцию, если она осталась активной
    Если ТранзакцияАктивна() Тогда
        ЗафиксироватьТранзакцию();
    КонецЕсли;
    
КонецЕсли;

Продвинутый уровень оптимизации: Использование временных таблиц

Проанализируем ситуацию, когда количество загружаемых штрихкодов измеряется десятками тысяч. Передача слишком большого массива в параметр запроса В (&МассивШтрихкодов) может замедлить работу оптимизатора SQL-сервера, так как текст запроса разрастается до огромных размеров.

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


// Создаем Таблицу Значений для передачи в менеджер временных таблиц
ТаблицаКодов = Новый ТаблицаЗначений;
ТаблицаКодов.Колонки.Добавить("Код", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));

МассивШтрихкодов = СтрРазделить(ИсходнаяСтрока, ";", Ложь);
Для Каждого ТекущийКод Из МассивШтрихкодов Цикл
    НоваяСтрока = ТаблицаКодов.Добавить();
    НоваяСтрока.Код = СокрЛП(ТекущийКод);
КонецЦикла;

// Используем менеджер временных таблиц для ускорения поиска на стороне СУБД
МенеджерВТ = Новый МенеджерВременных Таблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Шаг 1. Помещаем таблицу значений во временную таблицу и индексируем поле поиска
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВходныеКоды.Код КАК Код
    |ПОМЕСТИТЬ ВТ_Коды
    |ИЗ
    |	&ТаблицаКодов КАК ВходныеКоды
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ИНДЕКСИРОВАТЬ ВТ_Коды.Код
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ШтрихКоды.Ссылка КАК Ссылка
    |ИЗ
    |	ВТ_Коды КАК ВТ_Коды
    |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ШтрихКодыКовров КАК ШтрихКоды
    |		ПО ВТ_Коды.Код = ШтрихКоды.Код
    |ГДЕ
    |	НЕ ШтрихКоды.Оприходован";

Запрос.УстановитьПараметр("ТаблицаКодов", ТаблицаКодов);
РезультатЗапроса = Запрос.Выполнить();

Использование индексированной временной таблицы гарантирует максимальную производительность поиска на уровне SQL, так как СУБД использует быстрый алгоритм индексного соединения таблиц (Index Spool / Merge Join) вместо последовательного сканирования огромного списка параметров.

Таким образом, комбинируя современные методы разбора строк с пакетными транзакциями, свойством ОбменДанными.Загрузка и механизмами временных таблиц в запросах, мы получаем максимально производительное и отказоустойчивое решение для обработки данных в 1С:Предприятии 8.

← На главную