Как сравнить две таблицы значений во внешней обработке и найти отсутствующие данные?

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

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

Подготовительный этап: работа с таблицами на форме

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

Проанализируем пример кода для получения таблиц из реквизитов формы:


// Преобразуем реквизиты формы (коллекции данных) в объекты ТаблицаЗначений
ТЗ_1 = РеквизитФормыВЗначение("Объект.ТЗ_1", Тип("ТаблицаЗначений"));
ТЗ_2 = РеквизитФормыВЗначение("Объект.ТЗ_2", Тип("ТаблицаЗначений"));

После проведения всех манипуляций результат необходимо будет загрузить обратно в табличную часть формы с помощью метода ЗначениеВРеквизитФормы.

Способ 1: Использование запроса и временных таблиц

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

Разберем по шагам логику запроса:

  1. Создаем новый запрос и устанавливаем параметры.
  2. Передаем наши ТЗ в запрос. Важно, чтобы колонки таблиц были типизированы (имели явно заданный тип данных), иначе запрос может отработать некорректно.
  3. Выполняем ЛЕВОЕ СОЕДИНЕНИЕ второй таблицы к первой по ключевому полю (в нашем случае — Сотрудник).
  4. Устанавливаем фильтр ГДЕ ТЗ_1.Сотрудник ЕСТЬ NULL. Это позволит отобрать только те строки из ТЗ_2, для которых не нашлось соответствия в ТЗ_1.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ВТ_ТЗ2.Сотрудник КАК Сотрудник
    |ПОМЕСТИТЬ ВТ2
    |ИЗ
    |    &ТЗ_2 КАК ВТ_ТЗ2
    |;
    |
    |ВЫБРАТЬ
    |    ВТ_ТЗ1.Сотрудник КАК Сотрудник
    |ПОМЕСТИТЬ ВТ1
    |ИЗ
    |    &ТЗ_1 КАК ВТ_ТЗ1
    |;
    |
    |ВЫБРАТЬ
    |    ВТ2.Сотрудник
    |ИЗ
    |    ВТ2 КАК ВТ2
    |    ЛЕВОЕ СОЕДИНЕНИЕ ВТ1 КАК ВТ1
    |    ПО ВТ2.Сотрудник = ВТ1.Сотрудник
    |ГДЕ
    |    ВТ1.Сотрудник ЕСТЬ NULL";

Запрос.УстановитьПараметр("ТЗ_1", ТЗ_1);
Запрос.УстановитьПараметр("ТЗ_2", ТЗ_2);

Результат = Запрос.Выполнить().Выгрузить();
// Далее выгружаем результат в итоговую ТЧ
Объект.ТЧ.Загрузить(Результат);

Способ 2: Метод «весов» и сворачивание таблицы

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

Проанализируем алгоритм:

  1. Создаем вспомогательную таблицу ТЗ_Общая, копируя структуру колонок.
  2. Добавляем числовую колонку Итог.
  3. Заполняем общую таблицу данными из обеих исходных ТЗ.
  4. При заполнении из ТЗ_1 устанавливаем Итог = 1, а из ТЗ_2Итог = -1 (или наоборот, в зависимости от того, что ищем).
  5. Применяем метод Свернуть("Сотрудник", "Итог").

В результате строки, которые были в обеих таблицах, дадут в сумме 0 (1 + (-1) = 0). Строки, оставшиеся со значением -1 или 1, укажут на уникальные записи в соответствующих таблицах.


ТЗ_Общая = ТЗ_1.Скопировать();
ТЗ_Общая.Колонки.Добавить("Итог", Новый ОписаниеТипов("Число"));
ТЗ_Общая.ЗаполнитьЗначения(1, "Итог");

Для Каждого Стр Из ТЗ_2 Цикл
    НоваяСтр = ТЗ_Общая.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтр, Стр);
    НоваяСтр.Итог = -1;
КонецЦикла;

ТЗ_Общая.Свернуть("Сотрудник", "Итог");

// Удаляем те, что схлопнулись в ноль (значит были в обеих таблицах)
// И те, что были только в ТЗ_1 (имеют итог 1)
Индекс = ТЗ_Общая.Количество() - 1;
Пока Индекс >= 0 Цикл
    Если ТЗ_Общая[Индекс].Итог >= 0 Тогда
        ТЗ_Общая.Удалить(Индекс);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Способ 3: Оптимизированный поиск через «Соответствие» (Map)

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

Рассмотрим, как это реализовать:


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

// 2. Проходим по второй таблице и проверяем отсутствие
Для Каждого Стр Из ТЗ_2 Цикл
    Если КартаСотрудников.Получить(Стр.Сотрудник) = Неопределено Тогда
        // Сотрудника нет в ТЗ_1, добавляем в результат
        НоваяСтрока = Объект.ТЧ.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Стр);
    КонецЕсли;
КонецЦикла;

Важный момент: если вы все же используете метод ТЗ_1.Найти(Сотрудник, "Сотрудник"), обязательно предварительно добавьте индекс по этой колонке: ТЗ_1.Индексы.Добавить("Сотрудник"). Это сократит время поиска с линейного до логарифмического, что критично при больших объемах данных.

Выясним причины ошибок

Часто программисты сталкиваются с ситуацией, когда «в ТЧ попадают либо все, либо никто». Основные причины этого:

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

← На главную