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

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

При разработке в среде «1С:Предприятие» программисты регулярно сталкиваются с задачей заполнения табличных частей (ТЧ) документов или обработок результатами выполнения запросов. Стандартный метод Загрузить() полностью очищает табличную часть перед записью новых данных, что делает его неприменимым, когда в таблице уже присутствуют заполненные пользователем строки, которые необходимо сохранить.

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

Метод 1. Слияние на стороне СУБД через временные таблицы и ОБЪЕДИНИТЬ ВСЕ

Один из самых надежных и архитектурно правильных способов объединения — переложить задачу слияния данных на СУБД (MS SQL, PostgreSQL и др.). Этот метод отлично подходит для клиент-серверного варианта работы программы и минимизирует нагрузку на сервер приложений 1С.

Суть метода заключается в следующем:

  1. Выгрузить текущее состояние табличной части в промежуточную Таблицу Значений (ТЗ).
  2. Передать эту ТЗ в качестве параметра в запрос.
  3. Внутри запроса поместить ТЗ во временную таблицу.
  4. Объединить данные временной таблицы с результатом основного запроса с помощью конструкции ОБЪЕДИНИТЬ ВСЕ.
  5. Выгрузить общий результат обратно в табличную часть методом Загрузить().

Рассмотрим подробнее программную реализацию данного подхода на примере добавления новых записей о товарах:


// Шаг 1. Выгружаем текущие данные ТЧ в Таблицу Значений
ТЗ_ТекущиеДанные = СсылкаНаОбъект.Товары.Выгрузить();

// Шаг 2. Инициализируем запрос и передаем ТЗ в качестве параметра
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ_ТекущиеДанные", ТЗ_ТекущиеДанные);
Запрос.УстановитьПараметр("НовыйСклад", СкладПолучатель);

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

// Шаг 4. Выполняем запрос и загружаем результаты обратно
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
    СсылкаНаОбъект.Товары.Загрузить(РезультатЗапроса.Выгрузить());
КонецЕсли;

Важное примечание: Чтобы СУБД могла эффективно обработать этот запрос, структура колонок передаваемой таблицы значений ТЗ_ТекущиеДанные должна максимально точно соответствовать типам полей результирующей выборки. Несовпадение типов приведет к неявному приведению типов на уровне SQL-сервера, что снизит скорость обработки.

Метод 2. Сверхбыстрое слияние на уровне C++ платформы (Метод Гилева)

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

Если размер массива, передаваемого в метод ЗагрузитьКолонку(), меньше, чем общее количество строк в целевой таблице значений, то платформа заменяет только верхние элементы таблицы, а все нижние строки остаются без изменений. Используя это свойство, мы можем провернуть операцию слияния на уровне внутреннего кода C++ платформы 1С, что работает в десятки раз быстрее любого классического цикла.

Разберем алгоритм по шагам:

  1. Выгружаем текущую табличную часть в ТЗ_ТЧ.
  2. Выгружаем новые данные запроса в ТЗ_Запрос.
  3. Создаем копию новой таблицы: ТЗ_Результат = ТЗ_Запрос.Скопировать().
  4. Добавляем в начало таблицы ТЗ_Результат ровно столько пустых строк, сколько записей содержится в исходной ТЗ_ТЧ. Для этого используем метод Вставить(0).
  5. Для каждого реквизита последовательно переносим данные из ТЗ_ТЧ в верхнюю "пустую" область новой таблицы методом ЗагрузитьКолонку().
  6. Загружаем полученную результирующую ТЗ обратно в объект.

Посмотрим на пример программного кода, реализующего этот метод:


// 1. Получаем исходные таблицы
ТЗ_ТЧ = СсылкаНаОбъект.Товары.Выгрузить();
КоличествоСтрокТЧ = ТЗ_ТЧ.Количество();

ТЗ_Запрос = РезультатЗапроса.Выгрузить();

// 2. Создаем результирующий контейнер на основе структуры запроса
ТЗ_Результат = ТЗ_Запрос.Скопировать();

// 3. Быстро подготавливаем место под старые строки в начале таблицы
Для Счетчик = 1 По КоличествоСтрокТЧ Цикл
    ТЗ_Результат.Вставить(0);
КонецЦикла;

// 4. Поколоночно "накатываем" старые данные поверх пустых строк
Для Каждого Колонка Из ТЗ_ТЧ.Колонки Цикл
    ИмяКолонки = Колонка.Имя;
    // Проверяем, существует ли такая колонка в новой таблице
    Если ТЗ_Результат.Колонки.Найти(ИмяКолонки) <> Неопределено Тогда
        МассивЗначений = ТЗ_ТЧ.ВыгрузитьКолонку(ИмяКолонки);
        ТЗ_Результат.ЗагрузитьКолонку(МассивЗначений, ИмяКолонки);
    КонецЕсли;
КонецЦикла;

// 5. Загружаем объединенные данные обратно в объект
СсылкаНаОбъект.Товары.Загрузить(ТЗ_Результат);

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

Метод 3. Специфика обычных форм (обычный режим приложения)

Поскольку в обсуждении на форуме автор уточнил, что разработка ведется под обычный (неуправляемый) режим приложения, мы имеем колоссальное преимущество перед управляемым интерфейсом:

В этой связи использование метода Гилева (Метод 2) на толстом клиенте обычного приложения будет выполняться практически моментально даже на объемах в 500 000 строк.

Анализ опасных методов: Почему нельзя использовать ЗначениеВСтрокуВнутр?

В ходе обсуждения на форумах часто всплывает старый "хак" (dirty hack) времен платформы 1С 8.1 и ранних версий 8.2 — использование системных функций ЗначениеВСтрокуВнутр() и ЗначениеИзСтрокиВнутр() для низкоуровневой модификации текстового представления XML/JSON-сериализатора 1С.

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

Мы категорически не рекомендуем использовать этот подход сегодня по следующим причинам:

Резюме и рекомендации

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

  1. Если вы работаете в клиент-серверном варианте (особенно на управляемых формах), самым прозрачным и надежным методом является Метод 1 (Слияние через Запрос). Он позволяет полностью делегировать тяжелую работу по слиянию данных СУБД и возвращает на сервер приложений уже готовую структуру.
  2. Если критически важна максимальная скорость работы алгоритма без обращения к базам данных (например, при обработке локальных файлов в файловой базе), используйте Метод 2 (Поколоночное слияние Гилева). Этот прием задействует скрытые оптимизации ядра платформы на уровне C++ кода и отрабатывает в разы быстрее классических циклов.
← На главную