При разработке в среде «1С:Предприятие» программисты регулярно сталкиваются с задачей заполнения табличных частей (ТЧ) документов или обработок результатами выполнения запросов. Стандартный метод Загрузить() полностью очищает табличную часть перед записью новых данных, что делает его неприменимым, когда в таблице уже присутствуют заполненные пользователем строки, которые необходимо сохранить.
Если объем данных невелик, проблема легко решается простым перебором строк в цикле. Однако когда речь идет о десятках и сотнях тысяч строк, построчный обход силами виртуальной машины 1С начинает сильно тормозить систему. В этой статье мы разберем несколько эффективных способов решения этой задачи, проанализируем их производительность и выберем оптимальные алгоритмы для обычных и управляемых форм.
Один из самых надежных и архитектурно правильных способов объединения — переложить задачу слияния данных на СУБД (MS SQL, PostgreSQL и др.). Этот метод отлично подходит для клиент-серверного варианта работы программы и минимизирует нагрузку на сервер приложений 1С.
Суть метода заключается в следующем:
ОБЪЕДИНИТЬ ВСЕ.Загрузить().Рассмотрим подробнее программную реализацию данного подхода на примере добавления новых записей о товарах:
// Шаг 1. Выгружаем текущие данные ТЧ в Таблицу Значений
ТЗ_ТекущиеДанные = СсылкаНаОбъект.Товары.Выгрузить();
// Шаг 2. Инициализируем запрос и передаем ТЗ в качестве параметра
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ_ТекущиеДанные", ТЗ_ТекущиеДанные);
Запрос.УстановитьПараметр("НовыйСклад", СкладПолучатель);
// Шаг 3. Пишем текст запроса с использованием Временной Таблицы
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Номенклатура КАК Номенклатура,
| ТЗ.Количество КАК Количество,
| ТЗ.Цена КАК Цена,
| ТЗ.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_ТекущаяТЧ
|ИЗ
| &ТЗ_ТекущиеДанные КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Выбираем старые данные
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ВТ.Количество КАК Количество,
| ВТ.Цена КАК Цена,
| ВТ.Сумма КАК Сумма
|ИЗ
| ВТ_ТекущаяТЧ КАК ВТ
|
|ОБЪЕДИНИТЬ ВСЕ
|
|// Присоединяем новые данные из базы данных
|ВЫБРАТЬ
| ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
| 1 КАК Количество, // Значение по умолчанию
| ЦеныНоменклатурыСрезПоследних.Цена КАК Цена,
| ЦеныНоменклатурыСрезПоследних.Цена КАК Сумма
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, Склад = &НовыйСклад) КАК ЦеныНоменклатурыСрезПоследних";
// Шаг 4. Выполняем запрос и загружаем результаты обратно
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
СсылкаНаОбъект.Товары.Загрузить(РезультатЗапроса.Выгрузить());
КонецЕсли;
Важное примечание: Чтобы СУБД могла эффективно обработать этот запрос, структура колонок передаваемой таблицы значений ТЗ_ТекущиеДанные должна максимально точно соответствовать типам полей результирующей выборки. Несовпадение типов приведет к неявному приведению типов на уровне SQL-сервера, что снизит скорость обработки.
Существует изящный платформенный прием, позволяющий объединить две таблицы значений (текущую ТЗ из ТЧ и ТЗ из нового запроса) вообще без циклов обработки строк в виртуальной машине 1С. Этот метод базируется на особенности работы встроенного метода ЗагрузитьКолонку().
Если размер массива, передаваемого в метод ЗагрузитьКолонку(), меньше, чем общее количество строк в целевой таблице значений, то платформа заменяет только верхние элементы таблицы, а все нижние строки остаются без изменений. Используя это свойство, мы можем провернуть операцию слияния на уровне внутреннего кода C++ платформы 1С, что работает в десятки раз быстрее любого классического цикла.
Разберем алгоритм по шагам:
ТЗ_ТЧ.ТЗ_Запрос.ТЗ_Результат = ТЗ_Запрос.Скопировать().ТЗ_Результат ровно столько пустых строк, сколько записей содержится в исходной ТЗ_ТЧ. Для этого используем метод Вставить(0).ТЗ_ТЧ в верхнюю "пустую" область новой таблицы методом ЗагрузитьКолонку().Посмотрим на пример программного кода, реализующего этот метод:
// 1. Получаем исходные таблицы
ТЗ_ТЧ = СсылкаНаОбъект.Товары.Выгрузить();
КоличествоСтрокТЧ = ТЗ_ТЧ.Количество();
ТЗ_Запрос = РезультатЗапроса.Выгрузить();
// 2. Создаем результирующий контейнер на основе структуры запроса
ТЗ_Результат = ТЗ_Запрос.Скопировать();
// 3. Быстро подготавливаем место под старые строки в начале таблицы
Для Счетчик = 1 По КоличествоСтрокТЧ Цикл
ТЗ_Результат.Вставить(0);
КонецЦикла;
// 4. Поколоночно "накатываем" старые данные поверх пустых строк
Для Каждого Колонка Из ТЗ_ТЧ.Колонки Цикл
ИмяКолонки = Колонка.Имя;
// Проверяем, существует ли такая колонка в новой таблице
Если ТЗ_Результат.Колонки.Найти(ИмяКолонки) <> Неопределено Тогда
МассивЗначений = ТЗ_ТЧ.ВыгрузитьКолонку(ИмяКолонки);
ТЗ_Результат.ЗагрузитьКолонку(МассивЗначений, ИмяКолонки);
КонецЕсли;
КонецЦикла;
// 5. Загружаем объединенные данные обратно в объект
СсылкаНаОбъект.Товары.Загрузить(ТЗ_Результат);
Этот метод показывает феноменальные результаты по скорости, поскольку в нем нет тяжелого построчного создания объектов и вызовов методов ЗаполнитьЗначенияСвойств() в цикле 1С.
Поскольку в обсуждении на форуме автор уточнил, что разработка ведется под обычный (неуправляемый) режим приложения, мы имеем колоссальное преимущество перед управляемым интерфейсом:
ДанныеФормыВЗначение() и ЗначениеВДанныеФормы(). Мы можем изменять данные табличного поля напрямую.В этой связи использование метода Гилева (Метод 2) на толстом клиенте обычного приложения будет выполняться практически моментально даже на объемах в 500 000 строк.
В ходе обсуждения на форумах часто всплывает старый "хак" (dirty hack) времен платформы 1С 8.1 и ранних версий 8.2 — использование системных функций ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр() для низкоуровневой модификации текстового представления XML/JSON-сериализатора 1С.
Суть хака заключалась в том, что разработчики преобразовывали две таблицы значений в системные текстовые строки, вручную с помощью строковых функций объединяли их массивы данных, корректировали счетчик количества строк в служебном заголовке текста и конвертировали полученную строку обратно в объект "ТаблицаЗначений".
Мы категорически не рекомендуем использовать этот подход сегодня по следующим причинам:
Выбор оптимального метода зависит от конкретной архитектуры вашего решения: