gifts2017

Сравнение двух таблиц значений.

Опубликовал Евген Каравашкин (Lokiy) в раздел Программирование - Практика программирования

Возникла потребность сравнить данные в двух таблицах сказать равны ли они.
В результате появилась неплохая как по мне функция.
В ней показана возможность как сравнивать значения с определенной точностью,
заготовлен шаблон для исключения колонок для сравнения.
Надеюсь кому-то упростит/ускорит скорость разработки.
 
 
Функция СравнимТЗ(ТЗ1,ТЗ2)
	нпп=0;
	ПолностьюИдентичны=ИСТИНА;
	Для каждого строкаТЗ1 из ТЗ1 цикл
		Идентичны=Истина;
		Если нпп>=ТЗ2.Количество() тогда Сообщить("Добавлена строка "+нпп);нпп=нпп+1;продолжить; КонецЕсли;
		СтрокаТЗ2=ТЗ2.Получить(нпп);
		
		СписокПропускаемыхКолонок=",Период,Регистратор,Активность,МоментВремени,"; //зяпятые должны обрамлять
		Для каждого колонкаТЗ1 из ТЗ1.Колонки цикл
			Если Найти(СписокПропускаемыхКолонок,","+колонкаТЗ1.Имя+",")<>0 тогда продолжить;КонецЕсли;
			
			
			
			былиидентичны=Идентичны;
			Если ТипЗнч(СтрокаТЗ2[колонкаТЗ1.Имя])=Тип("Число") тогда  //допускаем прогрешность 0.001
				ПроверкаИдентичности=СтрокаТЗ2[колонкаТЗ1.Имя]-строкаТЗ1[колонкаТЗ1.Имя];
				ПроверкаИдентичности=?(ПроверкаИдентичности>0,ПроверкаИдентичности,-ПроверкаИдентичности);
				Если  ПроверкаИдентичности>0.001 тогда
					ПроверкаИдентичности=ложь;
				Иначе
					ПроверкаИдентичности=Истина;
				КонецЕсли;
				
				Идентичны=Идентичны И ПроверкаИдентичности;
				
			Иначе
				Идентичны=Идентичны И СтрокаТЗ2[колонкаТЗ1.Имя]=строкаТЗ1[колонкаТЗ1.Имя];	
				
			КонецЕсли;
			ПолностьюИдентичны=ПолностьюИдентичны И Идентичны;
			
			Если былиидентичны<>Идентичны тогда
				СтрокаОшибкиТЗ1="ТЗ1 : ";
				СтрокаОшибкиТЗ2="ТЗ2 : ";
				СписокПропускаемыхКолонок=",Период,Регистратор,Активность,МоментВремени,"; //зяпятые должны обрамлять
				Для каждого тек2колонкаТЗ1 из ТЗ1.Колонки цикл
					Если Найти(СписокПропускаемыхКолонок,","+тек2колонкаТЗ1.Имя+",")<>0 тогда продолжить;КонецЕсли;
					СтрокаОшибкиТЗ1=СтрокаОшибкиТЗ1+тек2колонкаТЗ1.Имя+"="+СокрЛп(СтрокаТЗ1[тек2колонкаТЗ1.Имя])+" ; ";
					СтрокаОшибкиТЗ2=СтрокаОшибкиТЗ2+тек2колонкаТЗ1.Имя+"="+СокрЛп(СтрокаТЗ2[тек2колонкаТЗ1.Имя])+" ; ";										
				КонецЦикла;
				СтрокаОшибкиТЗ1=Лев(СтрокаОшибкиТЗ1,СтрДлина(СтрокаОшибкиТЗ1)-3);
				СтрокаОшибкиТЗ2=Лев(СтрокаОшибкиТЗ2,СтрДлина(СтрокаОшибкиТЗ2)-3);
				Сообщить("Первое отличие в колонке:"+колонкаТЗ1.Имя);
				Сообщить(СтрокаОшибкиТЗ1);
				Сообщить(СтрокаОшибкиТЗ2);
				Сообщить("-------------------------------------------------------------------------------------------------------------------------");
				
				
			КонецЕсли;
		КонецЦикла;
		Состояние(ТЗ1.Количество()-нпп);
		нпп=нпп+1;
	КонецЦикла;
	
	возврат ПолностьюИдентичны;
	
КонецФункции

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Василий Казьмин (awk) 01.10.13 12:28
Не буду комментировать, дабы не обижать.
2. Виктор Бредихин (brodya) 01.10.13 12:44
Для каждого строкаТЗ1 из ТЗ1 цикл

Дальше читать не стал.
ТЗ1 = Новый ТаблицаЗначений;
ТЗ1.Колонки.Добавить("Первая");
а = ТЗ1.Добавить();
а.Первая = 1;

ТЗ2 = Новый ТаблицаЗначений;
ТЗ2.Колонки.Добавить("Первая");
а = ТЗ2.Добавить();
а.Первая = 1;
а = ТЗ2.Добавить();
а.Первая = 2;
...Показать Скрыть

Такие таблицы получаются "ПолностьюИдентичны".
3. Василий Казьмин (awk) 01.10.13 13:19
Как-то так проще, функциональней и надежней.


Функция Сравнить(ТаблицаОбразец, Таблица, СоответствиеКолонок=Неопределено, МассивИсключаемыхКолонок=Неопределено, Точность=Неопределено, ПравилаОкругления=Неопределено, ИгнорироватьИдентичныеВРезультате=Истина)
	// Проверка входящих параметров
	
	Если ПравилаОкругления=Неопределено Тогда
		ПравилаОкругления = РежимОкругления.Окр15как20;
	КонецЕсли;
	
	Массив = Новый Массив();
	Результат = Новый Структура("Имя, КоличествоВОбразце, Количество, Критерий","КоличествоСтрок",ТаблицаОбразец.Количество(),Таблица.Количество(),Таблица.Количество()=ТаблицаОбразец.Количество());
	Если Не ИгнорироватьИдентичныеВРезультате ИЛИ Не Результат.Критерий Тогда
		Массив.Добавить(Результат);
	КонецЕсли;
	
	Количество = Мин(ТаблицаОбразец.Количество(), Таблица.Количество()) - 1;
	ТипЧисло =  Тип("Число");
	ТипСтрока =  Тип("Строка");
	
	Если МассивИсключаемыхКолонок<>Неопределено и ТипЗнч(МассивИсключаемыхКолонок) = ТипСтрока Тогда
		МассивИсключаемыхКолонок = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(МассивИсключаемыхКолонок,,Истина);
	КонецЕсли;
	
	Если СоответствиеКолонок=Неопределено Тогда
		СоответствиеКолонок = Новый Соответствие;
		Для каждого Колонка Из ТаблицаОбразец.Колонки Цикл
			Если Таблица.Колонки.Найти(Колонка.Имя) = Неопределено 
				ИЛИ (МассивИсключаемыхКолонок<>Неопределено И
				МассивИсключаемыхКолонок.Найти(Колонка.Имя) <> Неопределено) Тогда
				Продолжить;
			КонецЕсли;
			СоответствиеКолонок.Вставить(Колонка.Имя, Колонка.Имя);
		КонецЦикла;
	КонецЕсли;
	
	// Это можно вынести в функцию без параметров по умолчанию
	
	Для ит = 0 По Количество Цикл
		СтрокаОбразец = ТаблицаОбразец[ит];
		Строка = Таблица[ит];
		Для каждого КиЗ Из СоответствиеКолонок Цикл
			ТипОбразец = ТипЗнч(СтрокаОбразец[КиЗ.Ключ]);
			Тип = ТипЗнч(Строка[КиЗ.Значение]);
			Результат = Новый Структура("Имя, Образец, Поле, Критерий, НомерСтроки");
			Результат.НомерСтроки = ит;
			Результат.Имя = "Сравнение строки №"+ит;
			Результат.Образец = КиЗ.Ключ;
			Результат.Поле = КиЗ.Значение;
			Если Точность<>Неопределено И Тип = ТипОбразец И ТипОбразец = ТипЧисло Тогда
				Результат.Критерий = Окр(СтрокаОбразец[КиЗ.Ключ], Точность, ПравилаОкругления) = Окр(Строка[КиЗ.Значение], Точность, ПравилаОкругления);
			Иначе
				Результат.Критерий = СтрокаОбразец[КиЗ.Ключ] = Строка[КиЗ.Значение];
			КонецЕсли;
			Если Не ИгнорироватьИдентичныеВРезультате ИЛИ Не Результат.Критерий Тогда
				Массив.Добавить(Результат);
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;
	Возврат Массив;
КонецФункции


...Показать Скрыть
boldinov; ildarovich; vkr; dour-dead; +4 Ответить 1
4. Яков Коган (Yashazz) 03.10.13 12:31
Есть такая вещь, как соединение таблиц в запросе. Если речь о сравнении колонок с типами, обрабатываемыми запросом (т.е. безо всяких там массивов и хранилищ значений), то можно и проще сделать, через запрос/СКД. А если ещё функции СКД подтянуть...
5. Сергей (ildarovich) 04.10.13 14:03
Не хочется обсуждать очень уязвимый для критики вариант из статьи, а вот вариант (3) вполне себе "зрелый". Выскажусь не в плане критики этого варианта (это хороший и практичный вариант), а в плане того, что можно сделать по-другому.
1) Симметричность. Было бы здорово сравнивать таблицы, не выбирая таблицу-образец. То есть, чтобы при неравном числе строк выводились бы элементы разницы. В данном же случае, когда в проверяемой таблице строк больше, чем в образце, это не делается.
2) Маппинг колонок можно сделать проще - без соответствия. Для этого достаточно скопировать исходные таблицы, задав копируемые колонки. То есть два параметра: имена колонок через запятую из первой таблицы в первом параметре и имена колонок через запятую из второй таблицы во втором параметре. Тогда сравнивать можно будет колонки копий с одинаковым номером. И не потребуется раскладывать строки сторонней функцией.
3) Чувствительность сравнения. Тут либо описывать правила сравнения для всех простых типов: для строкового учет регистра и длины (или даже расстояния расстояние левенштейна), для дат - период точности (до секунд, до дней), для чисел - число знаков, либо уж ничего не задавать, а всегда делать точное сравнение. Потому что иначе идет перекос в сторону сравнения числовых таблиц значений и так и нужно будет назвать функцию.
4) Тип результата. Кажется, лучше помещать дельту не в массив структур, а в таблицу значений "Разница" с колонками: номер строки, номер колонки, значение в первой таблице, значение во второй таблице. Так у нас будет меньше разных сущностей. И тогда легче будет манипулировать с результатом в дальнейшем - группировать строки, сортировать, отбирать и так далее.

Кстати, для точного сравнения без маппинга можно использовать
ЗначениеВСтрокуВнутр(Таблица1) = ЗначениеВСтрокуВнутр(Таблица2)
Михаська; Maximysis; wolfsoft; awk; +4 Ответить 1
6. Василий Казьмин (awk) 04.10.13 14:55
(5) ildarovich, Не могу не согласиться. Особенно если учесть, что функция написана целиком с 12-44 по 13-19 (не забываем прибавить время на чтение чужого, ...(вместо точек сами вставляйте) кода). :))) если в статье декларировать, что это шаблон (пусть будет идеальным), то скорость поиска/вспоминания шаблона, как мне кажется будет минут 10-20, что всего в два-три раза быстрее написания.

Когда писал специально применил похожий на статью подход (по индексно). Хотя для сравнения у пользователей есть прекрасный инструмент сравнение значений(от 1С) или KDIFF. Для программистов же важнее либо А=Б или А!=Б. Если А!=Б, то какие строки в каких столбцах.

Если уж делать различия типа стока добавлена/удалена/изменена, то не по принципу добавлены все строки которых больше чем в образце, а первая измененная строка делает измененными все строки после нее.
7. Василий Казьмин (awk) 04.10.13 15:03
(4) Yashazz, Ну таки да, но для этого, иногда, надо вызов сервера делать, что не всегда приемлемо.
8. Евген Каравашкин (Lokiy) 06.10.13 10:26
Вся критика принимается, но когда мне надо было функцию для сравнения таблиц побыстрому, я не нагуглил, поэтому выложил чтобы кто-то кому тоже надо будет быстро - нагуглит. Ну нету на stackoverflow 1Са, а зря :).
9. Андрей (AKV77) 22.05.14 14:58
Еще один вариант сравнения, который применяется в типовой УПП :
Функция СравнитьТаблицыЗначений(ТаблицаЗначений1, ТаблицаЗначений2)

	Если ТипЗнч(ТаблицаЗначений1) <> Тип("ТаблицаЗначений") ИЛИ ТипЗнч(ТаблицаЗначений2) <> Тип("ТаблицаЗначений") Тогда
		Возврат Ложь;
	КонецЕсли; 
	
	Если ТаблицаЗначений1.Количество() <> ТаблицаЗначений2.Количество() Тогда
		Возврат Ложь;
	КонецЕсли; 

	Если ТаблицаЗначений1.Колонки.Количество() <> ТаблицаЗначений2.Колонки.Количество() Тогда
		Возврат Ложь;
	КонецЕсли; 

	Для каждого Колонка Из ТаблицаЗначений1.Колонки Цикл
		
		Если ТаблицаЗначений2.Колонки.Найти(Колонка.Имя) = Неопределено Тогда
			Возврат Ложь;
		КонецЕсли;
		
		Для каждого СтрокаТаблицы Из ТаблицаЗначений1 Цикл
		
			Попытка
			
				Если СтрокаТаблицы[Колонка.Имя] <> ТаблицаЗначений2[ТаблицаЗначений1.Индекс(СтрокаТаблицы)][Колонка.Имя] Тогда
				
					Возврат Ложь;
				
				КонецЕсли;
			
			Исключение
				
				Возврат Ложь;
				
			КонецПопытки;
		
		КонецЦикла; 
	
	КонецЦикла; 
	
	Возврат Истина;
	
КонецФункции // СравнитьТаблицыЗначений()
...Показать Скрыть
10. Илларион Пак (pakill) 12.08.16 20:27
Перебор строк и колонок - это цикл в цикле, что не очень производительно.
Для решения задач типа:
- сравнить таблицы с учетом или без учета порядка строк
- поиск дублирующихся строк
удобно пользоваться методом таблицы значений Свернуть().

Идея такова:
1. Копируем обе таблицы в одну общую таблицу
2. В общую таблицу добавляем колонку "Показатель". Заполняем его следующим образом: в строках, полученных из первой таблицы ставим Показатель = 1, в строках из второй таблицы ставим Показатель = 2
3. Сворачиваем общую таблицу: ТаблицаОбщая.Свернуть(ВсеСравниваемыеКолонки, "Показатель");
4. После свертки совпавшие строки будут иметь Показатель = 3, а в несовпавших строках будет Показатель = 1, либо Показатель = 2

Таким образом, отсутстсвие строк с показателем, равным 1 или 2, является признаком равенства таблиц.

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

Вот процедура, реализующая данный подход

// Ищет отличия двух таблиц
//
// Параметры:
//		Таблица1                - ТаблицаЗначений или ТабличнаяЧасть - первая таблица
//		Таблица2                - ТаблицаЗначений или ТабличнаяЧасть - вторая таблица
//		МассивСтрокиТаблицы1    - Выходной параметр, массив строк Таблицы1 - строки Таблицы1, 
//		                          несовпадающие с соответствующими строками Таблицы2
//		МассивСтрокиТаблицы2    - Выходной параметр, массив строк Таблицы2 - строки Таблицы2,
//		                          несовпадающие с соответствующими строками Таблицы1
//		СтрокаПереченьКолонок   - Строка - имена колонок, по которым сравниваются строки таблиц.
//		                          Если пустая строка, то подразумеваются все колонки Таблицы1
//		СтрокаИсключемыеКолонки - Строка - колонки, которые не должны участвовать в сравнении
// 
// Описание: 
//		Процедура сравнивает таблицы, и возвращает несовпадающие строки в двух массивах
//		
// Пример:
//
//		Документ1 = ...
//		Документ2 = ...
//		СтрокиОтличия1 = Неопределено;
//		СтрокиОтличия2 = Неопределено;
//		НайтиОтличияТаблиц(Документ1.Товары, Документ2.Товары, СтрокиОтличия1, СтрокиОтличия2);
//		Если СтрокиОтличия1.Количество() + СтрокиОтличия2.Количество() = 0 Тогда
//			Сообщить("Табличные части документов совпадают");
//		КонецЕсли;
//		
Процедура НайтиОтличияТаблиц(Таблица1, Таблица2, МассивСтрокиТаблицы1, МассивСтрокиТаблицы2, СтрокаПереченьКолонок = "", СтрокаИсключемыеКолонки = "")
	
	#Область Определение_реального_перечня_колонок
		// Определение реального перечня колонок с учетом необязательных параметров
		
		// В области определяется переменная РеальныйПереченьКолонок и ничего больше
		
		СтруктураИсключаемыеКолонки = Новый Структура(СтрокаИсключемыеКолонки);
		
		Если ЗначениеЗаполнено(СтрокаПереченьКолонок) Тогда
			
			Если ЗначениеЗаполнено(СтрокаИсключемыеКолонки) Тогда
				// Странно, что заполнены оба необязательных параметра. 
				// Тем не менее, выполним это требование
				РеальныйПереченьКолонок = "";
				СтруктураПереченьКолонок = Новый Структура(СтрокаПереченьКолонок);
				
				Для каждого ЭлементСтруктуры Из СтруктураПереченьКолонок Цикл
					ИмяКолонки = ЭлементСтруктуры.Ключ;
					Если Не СтруктураИсключаемыеКолонки.Свойство(ИмяКолонки) Тогда
						РеальныйПереченьКолонок = РеальныйПереченьКолонок + ?(РеальныйПереченьКолонок = "", "", ", ") + ИмяКолонки;
					КонецЕсли;
				КонецЦикла;
			Иначе
				РеальныйПереченьКолонок = СтрокаПереченьКолонок;
			КонецЕсли;
			
		Иначе
			
			РеальныйПереченьКолонок = "";
			
			Если ТипЗнч(Таблица1) = Тип("ТаблицаЗначений") Тогда
				Колонки = Таблица1.Колонки;
			Иначе
				Колонки = Таблица1.ВыгрузитьКолонки().Колонки;
			КонецЕсли;
			
			Для каждого Колонка Из Колонки Цикл
				Если Не СтруктураИсключаемыеКолонки.Свойство(Колонка.Имя) Тогда
					РеальныйПереченьКолонок = РеальныйПереченьКолонок + ?(РеальныйПереченьКолонок = "", "", ", ") + Колонка.Имя;
				КонецЕсли;
			КонецЦикла;
			
		КонецЕсли;
	
	#КонецОбласти
	
	
	#Область Создание_общей_таблицы
	
		// Создается объединенная общая таблица, заполненная строками обеих таблиц
		// с двумя дополнительными системными колонками
		
		// В области определяется переменная ТаблицаОбщая и ничего больше		
		
		Если ТипЗнч(Таблица1) = Тип("ТаблицаЗначений") Тогда
			ТаблицаОбщая = Таблица1.Скопировать(, РеальныйПереченьКолонок);
		Иначе
			ТаблицаОбщая = Таблица1.Выгрузить(, РеальныйПереченьКолонок);
		КонецЕсли;
		
		ТаблицаОбщая.Колонки.Добавить("sys_ИндексСтроки");
		ТаблицаОбщая.Колонки.Добавить("sys_ПоказательТаблицы");
		
		ИндексСтроки = 0;
		Для каждого СтрокаОбщейТаблицы Из ТаблицаОбщая Цикл
			СтрокаОбщейТаблицы.sys_ИндексСтроки = ИндексСтроки;
			СтрокаОбщейТаблицы.sys_ПоказательТаблицы = 1;
			ИндексСтроки = ИндексСтроки + 1;
		КонецЦикла;
		
		ИндексСтроки = 0;
		Для каждого СтрокаТаблицы2 Из Таблица2 Цикл
			СтрокаОбщейТаблицы = ТаблицаОбщая.Добавить();
			ЗаполнитьЗначенияСвойств(СтрокаОбщейТаблицы, СтрокаТаблицы2);
			СтрокаОбщейТаблицы.sys_ИндексСтроки = ИндексСтроки;
			СтрокаОбщейТаблицы.sys_ПоказательТаблицы = 2;
			ИндексСтроки = ИндексСтроки + 1;
		КонецЦикла;
	
	#КонецОбласти
	
	
	// Сворачивание совпадающих строк
	
	ТаблицаОбщая.Свернуть("sys_ИндексСтроки, "  + РеальныйПереченьКолонок, "sys_ПоказательТаблицы");
	
	
	
	// Получение результатов
	
	МассивСтрокиТаблицы1 = Новый Массив;
	СтрокиСПоказателем1 = ТаблицаОбщая.НайтиСтроки(Новый Структура("sys_ПоказательТаблицы", 1));
	Для каждого СтрокаОбщейТаблицы Из СтрокиСПоказателем1 Цикл
		МассивСтрокиТаблицы1.Добавить(Таблица1[СтрокаОбщейТаблицы.sys_ИндексСтроки]);
	КонецЦикла;
	
	МассивСтрокиТаблицы2 = Новый Массив;
	СтрокиСПоказателем2 = ТаблицаОбщая.НайтиСтроки(Новый Структура("sys_ПоказательТаблицы", 2));
	Для каждого СтрокаОбщейТаблицы Из СтрокиСПоказателем2 Цикл
		МассивСтрокиТаблицы2.Добавить(Таблица2[СтрокаОбщейТаблицы.sys_ИндексСтроки]);
	КонецЦикла;
	
	
	// Замечание. Строки ОбщейТаблицы, у которых sys_ПоказательТаблицы = 3, это совпавшие
	// строки Таблицы1 и Таблицы2
	
КонецПроцедуры
...Показать Скрыть