Как сравнить две таблицы значений в 1С и найти изменения?

Программист IT и автоматизация бизнеса
← На главную

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

Метод 1: Использование запросов (самый производительный)

Этот подход является наиболее предпочтительным при работе с большими объемами данных, поскольку вся нагрузка по сравнению ложится на СУБД, что значительно быстрее программного перебора в 1С. Разберем два основных варианта.

Вариант А: Объединение с группировкой (UNION ALL & GROUP BY)

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

Рассмотрим по шагам:

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

Проанализируем результат группировки:

Измененные строки при таком подходе будут выглядеть как пара: одна строка с маркером 1 (старая версия) и одна строка с маркером -1 (новая версия).

Посмотрим на пример кода:


// Предположим, у нас есть две таблицы: СтараяТЗ и НоваяТЗ
// с колонками "Номенклатура", "Характеристика", "Количество"

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Данные.Номенклатура,
|	Данные.Характеристика,
|	Данные.Количество,
|	СУММА(Данные.МаркерИзменений) КАК Маркер
|ИЗ
|	(ВЫБРАТЬ
|		СтараяТаблица.Номенклатура КАК Номенклатура,
|		СтараяТаблица.Характеристика КАК Характеристика,
|		СтараяТаблица.Количество КАК Количество,
|		1 КАК МаркерИзменений
|	ИЗ
|		&СтараяТЗ КАК СтараяТаблица
|	
|	ОБЪЕДИНИТЬ ВСЕ
|	
|	ВЫБРАТЬ
|		НоваяТаблица.Номенклатура,
|		НоваяТаблица.Характеристика,
|		НоваяТаблица.Количество,
|		-1
|	ИЗ
|		&НоваяТЗ КАК НоваяТаблица) КАК Данные
|
|СГРУППИРОВАТЬ ПО
|	Данные.Номенклатура,
|	Данные.Характеристика,
|	Данные.Количество
|
|ИМЕЮЩИЕ
|	СУММА(Данные.МаркерИзменений) <> 0";

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

РезультатСравнения = Запрос.Выполнить().Выгрузить();

Для Каждого Строка Из РезультатСравнения Цикл
    Если Строка.Маркер = 1 Тогда
        Сообщить("Удалена строка: " + Строка.Номенклатура + ", " + Строка.Количество);
    ИначеЕсли Строка.Маркер = -1 Тогда
        Сообщить("Добавлена строка: " + Строка.Номенклатура + ", " + Строка.Количество);
    КонецЕсли;
КонецЦикла;

Вариант Б: Полное соединение (FULL JOIN)

Это классический способ сравнения двух наборов данных в SQL. Для этого у строк должен быть уникальный ключ (например, "Номенклатура" + "Характеристика" или специальный GUID строки).

  1. Загружаем обе таблицы во временные таблицы запроса.
  2. Выполняем ПОЛНОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ (FULL JOIN) по ключевым полям.
  3. Анализируем результат:
    • Удаленные строки: те, у которых поля из новой таблицы равны NULL (ЕСТЬ NULL).
    • Добавленные строки: те, у которых поля из старой таблицы равны NULL.
    • Потенциально измененные или неизмененные: строки, где ключи совпали. Здесь нужно дополнительно сравнить неключевые поля, например, Количество.

Пример запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ЕСТЬNULL(Старая.Номенклатура, Новая.Номенклатура) КАК Номенклатура,
|	ЕСТЬNULL(Старая.Характеристика, Новая.Характеристика) КАК Характеристика,
|	Старая.Количество КАК КоличествоСтарое,
|	Новая.Количество КАК КоличествоНовое,
|	ВЫБОР
|		КОГДА Старая.Номенклатура ЕСТЬ NULL
|			ТОГДА ""Добавлена""
|		КОГДА Новая.Номенклатура ЕСТЬ NULL
|			ТОГДА ""Удалена""
|		КОГДА Старая.Количество <> Новая.Количество
|			ТОГДА ""Изменена""
|		ИНАЧЕ ""Не изменилась""
|	КОНЕЦ КАК Статус
|ИЗ
|	&СтараяТЗ КАК Старая
|		ПОЛНОЕ СОЕДИНЕНИЕ &НоваяТЗ КАК Новая
|		ПО Старая.Номенклатура = Новая.Номенклатура
|			И Старая.Характеристика = Новая.Характеристика";

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

Результат = Запрос.Выполнить().Выгрузить();
// В таблице Результат будет колонка "Статус" с информацией по каждой строке

Метод 2: Программный перебор с оптимизацией

Простой перебор вложенными циклами (`Для каждого ... Внутри Для каждого ...`) крайне неэффективен на больших таблицах. Его можно и нужно оптимизировать с помощью индексации одной из таблиц.

Использование объекта Соответствие

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

Алгоритм следующий:

  1. Создаем объект Соответствие.
  2. Перебираем одну из таблиц (например, новую) и заполняем соответствие. В качестве ключа используем уникальный идентификатор строки (это может быть одно поле или строка из значений нескольких полей), а в качестве значения — саму строку таблицы (Структура).
  3. Перебираем вторую таблицу (старую) и для каждой строки ищем ее ключ в нашем Соответствии.
    • Если ключ не найден — значит, эта строка была в старой ТЗ, но ее нет в новой. Она удалена.
    • Если ключ найден — получаем из соответствия строку из новой ТЗ и сравниваем ее поля со старой строкой. Если есть различия — строка изменена. Если различий нет — не изменилась.
  4. Чтобы найти добавленные строки, нужно выполнить обратную операцию или немного усложнить алгоритм, помечая найденные в соответствии строки, а в конце пройтись по всем непомеченным.

Пример кода для поиска удаленных и измененных строк:


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

// Ищем удаленные и измененные строки
Для Каждого СтрокаСтарая Из СтараяТЗ Цикл
    Ключ = "" + СтрокаСтарая.Номенклатура.УникальныйИдентификатор() + "|" + СтрокаСтарая.Характеристика.УникальныйИдентификатор();
    
    СтрокаНовая = СоответствиеНовыхСтрок.Получить(Ключ);
    
    Если СтрокаНовая = Неопределено Тогда
        Сообщить("Удалена строка: " + СтрокаСтарая.Номенклатура);
    Иначе
        // Ключ найден, теперь сравниваем остальные поля
        Если СтрокаСтарая.Количество <> СтрокаНовая.Количество Тогда
             Сообщить("Изменена строка: " + СтрокаСтарая.Номенклатура + ". Было: " + СтрокаСтарая.Количество + ", стало: " + СтрокаНовая.Количество);
        КонецЕсли;
        
        // Пометим строку как обработанную, чтобы потом найти добавленные
        СоответствиеНовыхСтрок.Удалить(Ключ);
    КонецЕсли;
КонецЦикла;

// Все что осталось в Соответствии - это добавленные строки
Для Каждого Элемент Из СоответствиеНовыхСтрок Цикл
    Сообщить("Добавлена строка: " + Элемент.Значение.Номенклатура);
КонецЦикла;

Метод 3: Использование готовых функций БСП

Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), то можно воспользоваться готовой функцией. Она не даст детального отчета, но позволит быстро проверить, идентичны ли таблицы.

Функция ОбщегоНазначения.КоллекцииИдентичны()

Эта функция сравнивает две коллекции (в том числе и таблицы значений) и возвращает Истина, если они полностью идентичны (одинаковое количество строк и одинаковые значения в них), и Ложь в противном случае.

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


// Подключение к общему модулю может отличаться в зависимости от контекста
Если ОбщегоНазначения.КоллекцииИдентичны(СтараяТЗ, НоваяТЗ) Тогда
    Сообщить("Таблицы полностью идентичны. Изменений нет.");
Иначе
    Сообщить("В таблицах есть различия. Запускаем детальный анализ...");
    // ... здесь код для детального сравнения
КонецЕсли;

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

← На главную