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

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

В процессе разработки на платформе 1С часто возникает задача обновления данных в одной таблице значений (ТЗ) на основе информации из другой. Например, когда у нас есть основная таблица с данными и вспомогательная «справочная» таблица, и нам нужно обновить значения в определенной колонке, сопоставив строки по уникальному идентификатору или ключу. Рассмотрим наиболее эффективные и безопасные способы решения этой задачи, проанализируем их производительность и разберем конкретные примеры кода.

Метод 1. Использование запроса с динамическим формированием текста

Использование механизма запросов — это наиболее «правильный» с точки зрения архитектуры 1С подход, особенно если таблицы содержат тысячи строк. Основная сложность здесь заключается в том, что автор вопроса может не знать заранее весь перечень колонок в исходной таблице, кроме ключевой. Чтобы не перечислять все колонки вручную, мы можем сформировать текст запроса динамически.

Проанализируем алгоритм: мы обходим коллекцию Колонки исходной таблицы и строим фрагмент ВЫБРАТЬ. Если имя колонки совпадает с той, которую нужно заменить, мы используем функцию ЕСТЬNULL, чтобы подставить новое значение из второй таблицы.

Разберем пример реализации этого метода:


// ТЗ1 - основная таблица, ТЗ2 - таблица с новыми данными
// Ключ - "Колонка1", Заменяемая колонка - "Колонка2", Источник данных - "Колонка3"

МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;

// Помещаем обе таблицы во временные таблицы
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ1 ИЗ &ТЗ1 КАК Т1; 
               |ВЫБРАТЬ * ПОМЕСТИТЬ ВТ2 ИЗ &ТЗ2 КАК Т2;";
Запрос.УстановитьПараметр("ТЗ1", ТЗ1);
Запрос.УстановитьПараметр("ТЗ2", ТЗ2);
Запрос.Выполнить();

// Формируем текст основного запроса динамически
ТекстВыбора = "";
Для Каждого Колонка Из ТЗ1.Колонки Цикл
    Если ВРег(Колонка.Имя) = "КОЛОНКА2" Тогда
        ТекстВыбора = ТекстВыбора + "ЕСТЬNULL(ВТ2.Колонка3, ВТ1.Колонка2) КАК Колонка2,";
    Иначе
        ТекстВыбора = ТекстВыбора + "ВТ1." + Колонка.Имя + ",";
    EndЕсли;
КонецЦикла;

// Удаляем лишнюю запятую в конце
ТекстВыбора = Лев(ТекстВыбора, СтрДлина(ТекстВыбора) - 1);

Запрос.Текст = "ВЫБРАТЬ " + ТекстВыбора + " ИЗ ВТ1 КАК ВТ1 
               |ЛЕВОЕ СОЕДИНЕНИЕ ВТ2 КАК ВТ2 ПО ВТ1.Колонка1 = ВТ2.Колонка1";

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

Этот метод гарантирует, что структура вашей таблицы останется прежней, а данные обновятся только там, где найдено соответствие по Колонка1. Важно помнить, что если в ВТ2 ключ Колонка1 не уникален, произойдет «размножение» строк исходной таблицы, поэтому предварительно убедитесь в уникальности ключа во второй таблице.

Метод 2. Цикл с использованием индексов (оптимизированный поиск)

Если таблицы небольшого размера (до нескольких тысяч строк) или выполнение происходит на стороне клиента, где запросы недоступны, мы можем использовать обычный перебор. Однако критически важно использовать Индексы. Без индексации метод Найти() будет выполнять полный сканируемый перебор таблицы при каждой итерации, что превратит алгоритм в задачу с квадратичной сложностью O(n*m).

Рассмотрим, как правильно подготовить таблицу для быстрого поиска:


// Добавляем индекс по ключевой колонке для мгновенного поиска
ТЗ2.Индексы.Добавить("Колонка1");

Для Каждого СтрокаТЗ1 Из ТЗ1 Цикл
    // Метод Найти теперь работает максимально быстро благодаря индексу
    СтрокаПоиска = ТЗ2.Найти(СтрокаТЗ1.Колонка1, "Колонка1");
    
    Если СтрокаПоиска <> Неопределено Тогда
        СтрокаТЗ1.Колонка2 = СтрокаПоиска.Колонка3;
    КонецЕсли;
КонецЦикла;

Преимущество: этот способ не требует создания временных таблиц в базе данных и работает «на месте», не пересоздавая саму таблицу. Если Колонка1 во второй таблице может повторяться, и вам нужно найти все вхождения, используйте метод НайтиСтроки(), который возвращает массив найденных строк.

Метод 3. Использование объекта «Соответствие» (Map)

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

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


// Шаг 1: Выгружаем данные из справочной таблицы в соответствие
КартаЗначений = Новый Соответствие;
Для Каждого СтрТЗ2 Из ТЗ2 Цикл
    КартаЗначений.Вставить(СтрТЗ2.Колонка1, СтрТЗ2.Колонка3);
КонецЦикла;

// Шаг 2: Обходим основную таблицу и подставляем значения
Для Каждого СтрТЗ1 Из ТЗ1 Цикл
    НовоеЗначение = КартаЗначений.Получить(СтрТЗ1.Колонка1);
    Если НовоеЗначение <> Неопределено Тогда
        СтрТЗ1.Колонка2 = НовоеЗначение;
    КонецЕсли;
КонецЦикла;

Этот подход особенно эффективен, когда вторая таблица (источник данных) значительно меньше первой. Мы один раз тратим время на создание хеш-таблицы в памяти, а затем получаем данные из нее за константное время.

Сравнение производительности и рекомендации

Проанализируем ситуацию: какой метод выбрать в конкретном случае? Золотое правило разработки на 1С гласит: всё, что можно сделать запросом на сервере, нужно делать запросом. Однако есть нюансы:

  1. Объем данных: Если в таблицах более 10 000 записей, запрос через МенеджерВременныхТаблиц будет работать быстрее за счет оптимизации на уровне SQL-сервера.
  2. Место выполнения: Если таблицы уже находятся на клиенте (например, в форме), передача их на сервер для выполнения запроса может занять больше времени (из-за сериализации), чем простой цикл с индексом прямо на клиенте.
  3. Типы данных: Запрос может изменить типы колонок (например, длину строки или точность числа), если не использовать типизацию при выгрузке. Цикл же сохраняет исходные объекты СтрокаТаблицыЗначений.

Ошибки, которых следует избегать

Рассмотрим метод ЗагрузитьКолонку(), упомянутый в обсуждении. Это крайне опасный инструмент в данной задаче. Метод просто копирует массив значений в колонку таблицы по порядку индексов строк (0, 1, 2...). Он не учитывает ключи. Если порядок строк в ТЗ1 и ТЗ2 хотя бы немного отличается, или в одной из таблиц отсутствует какая-то строка, данные «съедут», и значения будут присвоены неверным записям. Использовать ЗагрузитьКолонку() можно только в том случае, если вы на 100% уверены в полной идентичности состава и сортировки обеих таблиц, что на практике встречается крайне редко.

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

← На главную