Задача сравнения двух таблиц значений (ТЗ) — одна из наиболее часто встречающихся в практике разработчика 1С. Это может быть сопоставление данных при импорте, поиск расхождений между остатками в разных системах или проверка изменений в табличных частях документов. Рассмотрим подробно наиболее эффективные методы решения этой задачи, проанализируем их производительность и выберем лучшие практики.
Многие разработчики по привычке пытаются использовать Запрос для сравнения двух ТаблицаЗначений, помещая их во временные таблицы. Однако, как показывает практика и замеры производительности, этот метод имеет «узкое место» — передачу данных из оперативной памяти в менеджер временных таблиц. Рассмотрим пример кода, который просто передает данные в запрос без сложной обработки:
Функция СравнениеТаблицЗначений_Запрос(Таблица1, Таблица2)
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Передача данных во временные таблицы — самая долгая часть
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ Т1 ИЗ &Таблица1 КАК Т1;
|ВЫБРАТЬ * ПОМЕСТИТЬ Т2 ИЗ &Таблица2 КАК Т2;";
Запрос.УстановитьПараметр("Таблица1", Таблица1);
Запрос.УстановитьПараметр("Таблица2", Таблица2);
Запрос.Выполнить();
// Само сравнение (например, FULL JOIN) отработает быстро на стороне SQL,
// но общие затраты времени могут быть выше, чем у методов в памяти.
КонецФункции
Анализ показывает, что при объемах до 50 000 – 100 000 строк методы, работающие непосредственно в оперативной памяти (в коллекции), зачастую обходят запросы по скорости именно из-за отсутствия накладных расходов на сериализацию данных для SQL-сервера.
Если нам нужно просто быстро узнать, идентичны ли таблицы (без поиска конкретных строк), можно воспользоваться системной функцией ЗначениеВСтрокуВнутр. Важно помнить, что таблицы должны быть предварительно отсортированы по одинаковым правилам.
Функция ТаблицыИдентичны(Таблица1, Таблица2, КолонкиСортировки)
Если Таблица1.Количество() <> Таблица2.Количество() Тогда
Возврат Ложь;
КонецЕсли;
Таблица1.Сортировать(КолонкиСортировки);
Таблица2.Сортировать(КолонкиСортировки);
Возврат ЗначениеВСтрокуВнутр(Таблица1) = ЗначениеВСтрокуВнутр(Таблица2);
КонецФункции
Важный момент: этот способ крайне эффективен по написанию (всего одна строка), но он потребляет много памяти на больших объемах и не позволяет выделить конкретные изменения.
Разберем один из самых элегантных методов поиска различий. Идея заключается в объединении двух таблиц, назначении им признаков «Знак» (1 и -1) и последующей группировке. Проанализируем реализацию этого алгоритма:
Знак.ТЗ.Знак.Результат будет следующим: если после свертки Знак = 0, то строки в обеих таблицах были идентичны. Если Знак = 1 — строка есть только в первой таблице. Если Знак = -1 — только во второй.
Функция СравнениеМетодомСвертки(Т1, Т2, ИменаКолонок)
ОбщаяТЗ = Т1.Скопировать();
ОбщаяТЗ.Колонки.Добавить("Знак");
ОбщаяТЗ.ЗаполнитьЗначения(1, "Знак");
Для Каждого СтрТ2 Из Т2 Цикл
НоваяСтр = ОбщаяТЗ.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтр, СтрТ2);
НоваяСтр.Знак = -1;
КонецЦикла;
ОбщаяТЗ.Свернуть(ИменаКолонок, "Знак");
// Удаляем идентичные строки (где Знак = 0)
МертвыеСтроки = ОбщаяТЗ.НайтиСтроки(Новый Структура("Знак", 0));
Для Каждого Стр Из МертвыеСтроки Цикл
ОбщаяТЗ.Удалить(Стр);
КонецЦикла;
Возврат ОбщаяТЗ;
КонецФункции
Рассмотрим ситуацию, когда одна таблица является «эталоном», а вторую нужно проверить на изменения. Использование объекта Соответствие позволяет реализовать поиск со скоростью O(1), что критически важно для больших массивов данных.
Функция СравнитьЧерезСоответствие(Т1, Т2)
Ответ = Новый Соответствие;
ХэшКарта = Новый Соответствие;
// Индексируем вторую таблицу в памяти
Для Каждого Строка Из Т2 Цикл
ХэшКарта.Вставить(Строка[0], Строка); // Предполагаем, что колонка 0 - ключ
КонецЦикла;
Для Каждого Ведущая Из Т1 Цикл
Ключ = Ведущая[0];
Ведомая = ХэшКарта[Ключ];
Если Ведомая = Неопределено Тогда
Ответ.Вставить(Ключ, "Удалена");
Иначе
// Построчное сравнение колонок
Для ё = 1 По Т1.Колонки.Количество() - 1 Цикл
Если Ведущая[ё] <> Ведомая[ё] Тогда
Ответ.Вставить(Ключ, "Изменена");
Прервать;
КонецЕсли;
КонецЦикла;
ХэшКарта.Удалить(Ключ);
КонецЕсли;
КонецЦикла;
// Остатки в ХэшКарте — это добавленные строки
Для Каждого Элемент Из ХэшКарта Цикл
Ответ.Вставить(Элемент.Ключ, "Добавлена");
КонецЦикла;
Возврат Ответ;
КонецФункции
Выясним причину высокой скорости этого метода: Соответствие в 1С реализовано как хэш-таблица. Поиск ключа в ней не зависит от количества строк, в отличие от метода НайтиСтроки() у таблицы значений без индексов.
Если таблицы уже отсортированы по ключевой колонке (как указано в вопросе автора), наиболее оптимальным по памяти будет алгоритм слияния. Мы одновременно перебираем два индекса, двигаясь по таблицам параллельно.
Разберем логику сравнения:
Ключ1 < Ключ2 — строка 1 была удалена, сдвигаем индекс первой таблицы.Ключ1 > Ключ2 — строка 2 была добавлена, сдвигаем индекс второй таблицы.Ключ1 = Ключ2 — проверяем остальные колонки на равенство и сдвигаем оба индекса.Посмотрим на оптимизированный код цикла:
Индекс1 = 0; Индекс2 = 0;
ВГраница1 = Т1.Количество() - 1;
ВГраница2 = Т2.Количество() - 1;
Пока Индекс1 <= ВГраница1 И Индекс2 <= ВГраница2 Цикл
Стр1 = Т1[Индекс1];
Стр2 = Т2[Индекс2];
Ключ1 = Стр1[0];
Ключ2 = Стр2[0];
Если Ключ1 < Ключ2 Тогда
// Обработка удаления
Индекс1 = Индекс1 + 1;
ИначеЕсли Ключ1 > Ключ2 Тогда
// Обработка добавления
Индекс2 = Индекс2 + 1;
Иначе
// Ключи равны, сравниваем данные
Для ё = 1 По ВГраницаКолонок Цикл
Если Стр1[ё] <> Стр2[ё] Тогда
// Строка изменена
Прервать;
КонецЕсли;
КонецЦикла;
Индекс1 = Индекс1 + 1;
Индекс2 = Индекс2 + 1;
КонецЕсли;
КонецЦикла;
Рассмотрим несколько хитростей, которые помогут ускорить работу кода в разы:
Строка["ИмяКолонки"] работает медленнее, чем по индексу Строка[0]. Если вы планируете большой цикл, заранее получите индексы колонок.СравнениеЗначений работает стабильнее и быстрее в универсальных алгоритмах.ХешированиеДанных.НайтиСтроки(), обязательно добавьте индекс: Таблица.Индексы.Добавить("КолонкаКлюч"). Это превращает линейный поиск в логарифмический.Проанализировав ситуацию, можно сделать вывод: для разового сравнения небольших таблиц (до 1000 строк) лучше использовать Метод 2 (Свертка) из-за простоты реализации. Для высоконагруженных участков кода и больших объемов данных следует выбирать Метод 3 (Соответствие) или Метод 4 (Слияние), так как они обеспечивают наилучшую производительность.