Поиск и удаление дублей / Модуль объекта

файл
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2024, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда

#Область СлужебныеПроцедурыИФункции

// Определение менеджера объекта для вызова прикладных правил.
//
// Параметры:
//   ИмяОбластиПоискаДанных - Строка - имя области (полное имя метаданных).
//
// Возвращаемое значение:
//   СправочникиМенеджер, ПланыВидовХарактеристикМенеджер,
//   ПланыСчетовМенеджер, ПланыВидовРасчетаМенеджер - менеджер объекта.
//
Функция МенеджерОбластиПоискаДублей(Знач ИмяОбластиПоискаДанных) Экспорт
	ОбластьПоискаДанных = ОбщегоНазначения.ОбъектМетаданныхПоПолномуИмени(ИмяОбластиПоискаДанных);
	
	Если Метаданные.Справочники.Содержит(ОбластьПоискаДанных) Тогда
		Возврат Справочники[ОбластьПоискаДанных.Имя];
		
	ИначеЕсли Метаданные.ПланыВидовХарактеристик.Содержит(ОбластьПоискаДанных) Тогда
		Возврат ПланыВидовХарактеристик[ОбластьПоискаДанных.Имя];
		
	ИначеЕсли Метаданные.ПланыСчетов.Содержит(ОбластьПоискаДанных) Тогда
		Возврат ПланыСчетов[ОбластьПоискаДанных.Имя];
		
	ИначеЕсли Метаданные.ПланыВидовРасчета.Содержит(ОбластьПоискаДанных) Тогда
		Возврат ПланыВидовРасчета[ОбластьПоискаДанных.Имя];
		
	КонецЕсли;
	
	ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
		НСтр("ru = 'Некорректный тип объекта метаданных ""%1""'"), ИмяОбластиПоискаДанных);
КонецФункции

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

	Результат = Новый Структура;
	Результат.Вставить("ТаблицаДублей", ТаблицаДублей);
	Результат.Вставить("ОписаниеОшибки");
	Результат.Вставить("ВозвращеноМеньшеЧемНайдено", Ложь);
	Результат.Вставить("МестаИспользования");
	
	ТаблицаКандидатов = ТаблицаКандидатов();
	ПараметрыПоискаПохожихСтрок = Неопределено;
	КоличествоДублей = 0;
	
	Пока СледующийЭлементВыборки(СхемаЗапроса.ВыборкаЭталонныхОбъектов) Цикл
		ЭталонныйЭлемент = СхемаЗапроса.ВыборкаЭталонныхОбъектов.ТекущийЭлемент;
		
		// Установка отборов для выбора кандидатов.
		Для Каждого ЭлементОтбора Из СхемаЗапроса.ОтборыКандидатов Цикл
			ЭлементОтбора.Значение.ПравоеЗначение = ЭталонныйЭлемент[ЭлементОтбора.Ключ];
		КонецЦикла;
		
		// Выборка кандидатов дублей.
		ВыборкаКандидатов = ИнициализироватьВыборкуКД(СхемаЗапроса.СхемаКД, СхемаЗапроса.НастройкиКД);
		КандидатыДублей = ВыборкаКандидатов.ПроцессорВыводаКД.Вывести(ВыборкаКандидатов.ПроцессорКД);
		
		Если СтруктураЗапроса.ИменаПолейДляСравненияНаПодобие.Количество() > 0 Тогда
			
			СравнениеСтрокНаПодобие = ПрикладныеПараметрыПоиска.СравнениеСтрокНаПодобие;
			Если ПараметрыПоискаПохожихСтрок = Неопределено Тогда
				Попытка
					ПараметрыПоискаПохожихСтрок = ПоискИУдалениеДублей.ПараметрыПоискаПохожихСтрок();
				Исключение
					Результат.ОписаниеОшибки = 
						НСтр("ru = 'Не удалось подключить компоненту для нечеткого поиска дублей. Подробнее см. в журнале регистрации.'");
					Возврат Результат;
				КонецПопытки;
			КонецЕсли;
			ЗаполнитьЗначенияСвойств(ПараметрыПоискаПохожихСтрок, СравнениеСтрокНаПодобие);
			
			Для Каждого ИмяПоля Из СтруктураЗапроса.ИменаПолейДляСравненияНаПодобие Цикл
				
				ЗначенияПоля = Новый Массив;
				Для каждого СтрокаТаблицы Из КандидатыДублей Цикл
					ЗначенияПоля.Добавить(СтрЗаменить(СтрокаТаблицы[ИмяПоля], "~", "\u126"));
				КонецЦикла;
				ИскомыеСтроки = СтрСоединить(ЗначенияПоля, "~");
				СтрокаДляПоиска = ЭталонныйЭлемент[ИмяПоля];
				
				ИндексыСтрок = ПоискИУдалениеДублей.НайтиПохожиеСтроки(ИскомыеСтроки, СтрокаДляПоиска, "~", ПараметрыПоискаПохожихСтрок);
				Если ПустаяСтрока(ИндексыСтрок) Тогда
					Продолжить;
				КонецЕсли;
				
				Для Каждого ИндексСтроки Из СтрРазделить(ИндексыСтрок, ",") Цикл
					Если ПустаяСтрока(ИндексСтроки) Тогда
						Продолжить;
					КонецЕсли;
					ЭлементДубль = КандидатыДублей[Число(ИндексСтроки)];
					
					Если ЕстьПравилаПоиска Тогда
						ДобавитьСтрокуКандидатов(КоллекцияДублей, ТаблицаКандидатов, ЭталонныйЭлемент, ЭлементДубль, 
							СтруктураЗапроса);
						Если ТаблицаКандидатов.Количество() = КоличествоЭлементовДляСравнения Тогда
							ЗарегистрироватьДублиПоПрикладнымПравилам(КоллекцияДублей, ПолноеИмяОбъектаМетаданных, 
								МенеджерОбластиПоиска, ЭталонныйЭлемент, ТаблицаКандидатов, СтруктураЗапроса, 
								ДополнительныеПараметры);
							ТаблицаКандидатов.Очистить();
						КонецЕсли;
					Иначе
						ЗарегистрироватьДубль(КоллекцияДублей, ЭталонныйЭлемент, ЭлементДубль, СтруктураЗапроса);
					КонецЕсли;
					
				КонецЦикла;
				
			КонецЦикла;
		Иначе
			Для Каждого ЭлементДубль Из КандидатыДублей Цикл
				
				Если ЕстьПравилаПоиска Тогда
					ДобавитьСтрокуКандидатов(КоллекцияДублей, ТаблицаКандидатов, ЭталонныйЭлемент, ЭлементДубль, 
						СтруктураЗапроса);
					Если ТаблицаКандидатов.Количество() = КоличествоЭлементовДляСравнения Тогда
						ЗарегистрироватьДублиПоПрикладнымПравилам(ТаблицаДублей, ПолноеИмяОбъектаМетаданных, 
							МенеджерОбластиПоиска, ЭталонныйЭлемент, ТаблицаКандидатов, СтруктураЗапроса, 
							ДополнительныеПараметры);
						ТаблицаКандидатов.Очистить();
					КонецЕсли;
				Иначе
					ЗарегистрироватьДубль(КоллекцияДублей, ЭталонныйЭлемент, ЭлементДубль, СтруктураЗапроса);
				КонецЕсли;
				
			КонецЦикла;
		КонецЕсли;
		
		// Обрабатываем остаток таблицы для прикладных правил.
		Если ЕстьПравилаПоиска Тогда
			ЗарегистрироватьДублиПоПрикладнымПравилам(КоллекцияДублей, ПолноеИмяОбъектаМетаданных, МенеджерОбластиПоиска, 
				ЭталонныйЭлемент, ТаблицаКандидатов, СтруктураЗапроса, ДополнительныеПараметры);
			ТаблицаКандидатов.Очистить();
		КонецЕсли;
		
		УдалитьНезначимыеДубли(КоллекцияДублей);	
		УдалитьНезначимыеГруппы(КоллекцияДублей);
		
		Если КоличествоДублей <> ТаблицаДублей.Количество() Тогда
			КоличествоДублей = ТаблицаДублей.Количество();
			ДлительныеОперации.СообщитьПрогресс(КоличествоДублей, "ЗарегистрироватьДубль");
		КонецЕсли;

		Если РазмерВозвращаемойПорции > 0 И (ТаблицаДублей.Количество() > РазмерВозвращаемойПорции) Тогда
				Результат.ОписаниеОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = 'Найдено слишком много дублей. Показаны только первые %1.'"), РазмерВозвращаемойПорции); 
				Результат.ВозвращеноМеньшеЧемНайдено = Истина;
			Прервать;
		КонецЕсли;
				
	КонецЦикла;
	
	Если РассчитыватьМестаИспользования Тогда
		
		ДлительныеОперации.СообщитьПрогресс(0, "РассчитыватьМестаИспользования");
		
		СсылкиОбъектов = Новый Массив;
		Для Каждого СтрокаДублей Из ТаблицаДублей Цикл
			Если ЗначениеЗаполнено(СтрокаДублей.Ссылка) Тогда
				СсылкиОбъектов.Добавить(СтрокаДублей.Ссылка);
			КонецЕсли;
		КонецЦикла;
		
		МестаИспользования = ОбщегоНазначения.МестаИспользования(СсылкиОбъектов);
		МестаИспользования = МестаИспользования.Скопировать(
			МестаИспользования.НайтиСтроки(Новый Структура("ВспомогательныеДанные", Ложь)));
		МестаИспользования.Индексы.Добавить("Ссылка");
		Результат.МестаИспользования = МестаИспользования;
		
	КонецЕсли;
	
	Возврат Результат;
КонецФункции

// Определение наличия прикладных правил у объекта.
//
// Параметры:
//     МенеджерОбласти - СправочникМенеджер - менеджер проверяемого объекта.
//
// Возвращаемое значение:
//     Булево - Истина, если прикладные правила определены.
//
Функция ЕстьПрикладныеПравилаОбластиПоискаДублей(Знач ИмяОбъекта) Экспорт
	
	СведенияОбОбъекте = ПоискИУдалениеДублей.ОбъектыСПоискомДублей()[ИмяОбъекта];
	Возврат СведенияОбОбъекте <> Неопределено 
		И (СведенияОбОбъекте = "" Или СтрНайти(СведенияОбОбъекте, "ПараметрыПоискаДублей") > 0);
	
КонецФункции

// Обработчик фонового поиска дублей, вызывается из процедуры НайтиИУдалитьДубли формы ПоискДублей.
//
// Параметры:
//   ПараметрыПоиска - см. ПоискИУдалениеДублей.ПараметрыПоискаДублей
//   АдресРезультата - Строка - адрес во временном хранилище для возврата результата.
//
Процедура ФоновыйПоискДублей(Знач Параметры, Знач АдресРезультата) Экспорт
	
	НачалоПоискаДублей = ТекущаяДатаСеанса();
	ЗаписьЖурналаРегистрации(ПоискИУдалениеДублей.НаименованиеПодсистемы(Ложь),
		УровеньЖурналаРегистрации.Информация,,,
		СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Начат поиск дублей для: %1.'"), Параметры.ОбластьПоискаДублей));

	КомпоновщикПредварительногоОтбора = Новый КомпоновщикНастроекКомпоновкиДанных;
	КомпоновщикПредварительногоОтбора.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(Параметры.СхемаКомпоновки));
	КомпоновщикПредварительногоОтбора.ЗагрузитьНастройки(Параметры.НастройкиКомпоновщикаПредварительногоОтбора);
	Параметры.Вставить("КомпоновщикПредварительногоОтбора", КомпоновщикПредварительногоОтбора);
	
	ПравилаПоиска = Новый ТаблицаЗначений;
	ПравилаПоиска.Колонки.Добавить("Реквизит", Новый ОписаниеТипов("Строка") );
	ПравилаПоиска.Колонки.Добавить("Правило",  Новый ОписаниеТипов("Строка") );
	ПравилаПоиска.Индексы.Добавить("Реквизит");
	Для Каждого Правило Из Параметры.ПравилаПоиска Цикл
		ЗаполнитьЗначенияСвойств(ПравилаПоиска.Добавить(), Правило);
	КонецЦикла;
	Параметры.Вставить("ПравилаПоиска", ПравилаПоиска);
	Параметры.Вставить("РассчитыватьМестаИспользования", Истина);
	
	Результат = ГруппыДублей(Параметры);
	ПоместитьВоВременноеХранилище(Результат, АдресРезультата);
	
	ЗаписьЖурналаРегистрации(ПоискИУдалениеДублей.НаименованиеПодсистемы(Ложь),
		УровеньЖурналаРегистрации.Информация,,,
		СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Завершен поиск дублей для ""%1"" за %2 мин.'"), 
			Параметры.ОбластьПоискаДублей,
			Формат((ТекущаяДатаСеанса() - НачалоПоискаДублей) / 60, "ЧДЦ=2")));

КонецПроцедуры

// Обработчик фонового удаления дублей, вызывается из процедуры НайтиИУдалитьДубли формы ПоискДублей.
//
// Параметры:
//     Параметры - Структура - данные для анализа.
//     
// Возвращаемое значение:
//   см. ОбщегоНазначения.ЗаменитьСсылки
//
Функция ФоновоеУдалениеДублей(Знач Параметры) Экспорт
	
	НачалоУдаленияДублей = ТекущаяДатаСеанса();
	КоличествоДублей = Параметры.ПарыЗамен.Количество(); 
	ЗаписьЖурналаРегистрации(ПоискИУдалениеДублей.НаименованиеПодсистемы(Ложь),
		УровеньЖурналаРегистрации.Информация,,,
		СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Начато удаление дублей: %1.'"), КоличествоДублей));

	ПараметрыЗамены = Новый Структура;
	ПараметрыЗамены.Вставить("ВключатьБизнесЛогику", Ложь);
	ПараметрыЗамены.Вставить("УчитыватьПрикладныеПравила", Параметры.УчитыватьПрикладныеПравила);
	ПараметрыЗамены.Вставить("ЗаменаПарыВТранзакции", Ложь);
	ПараметрыЗамены.Вставить("СпособУдаления", Параметры.СпособУдаления);
	
	Результат = ОбщегоНазначения.ЗаменитьСсылки(Параметры.ПарыЗамен, ПараметрыЗамены);
	ЗаписьЖурналаРегистрации(ПоискИУдалениеДублей.НаименованиеПодсистемы(Ложь),
		УровеньЖурналаРегистрации.Информация,,,
		СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Завершено удаление дублей %1 за %2 мин.'"), 
			КоличествоДублей,
			Формат((ТекущаяДатаСеанса() - НачалоУдаленияДублей) / 60, "ЧДЦ=2")));

	Возврат Результат;
	
КонецФункции

// Преобразуем объект в таблицу для помещения в запрос.
Функция ОбъектВТаблицуЗначений(Знач ОбъектДанных, Знач РасшифровкаДополнительныхПолей)
	Результат = Новый ТаблицаЗначений;
	СтрокаДанных = Результат.Добавить();
	
	МетаданныеОбъекты = ОбъектДанных.Метаданные();
	
	Для Каждого МетаРеквизит Из МетаданныеОбъекты.СтандартныеРеквизиты  Цикл
		Имя = МетаРеквизит.Имя;
		Результат.Колонки.Добавить(Имя, МетаРеквизит.Тип);
		СтрокаДанных[Имя] = ОбъектДанных[Имя];
	КонецЦикла;
	
	Для Каждого МетаРеквизит Из МетаданныеОбъекты.Реквизиты Цикл
		Имя = МетаРеквизит.Имя;
		Результат.Колонки.Добавить(Имя, МетаРеквизит.Тип);
		СтрокаДанных[Имя] = ОбъектДанных[Имя];
	КонецЦикла;
	
	Для Каждого КлючИЗначение Из РасшифровкаДополнительныхПолей Цикл
		Имя1 = КлючИЗначение.Ключ;
		Имя2 = КлючИЗначение.Значение;
		Результат.Колонки.Добавить(Имя1, Результат.Колонки[Имя2].ТипЗначения);
		СтрокаДанных[Имя1] = СтрокаДанных[Имя2];
	КонецЦикла;
	
	Возврат Результат;
КонецФункции

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

// Добавить строку в таблицу кандидатов для прикладного варианта поиска дублей.
//
// Параметры:
//   ТаблицаКандидатов - см. ТаблицаКандидатов
//   ДанныеОсновногоЭлемента - Структура:
//     * Ссылка - ЛюбаяСсылка
//     * Наименование - Строка
//     * Код - Строка
//     * ПометкаУдаления - Булево
//   ДанныеКандидата - Структура:
//     * Ссылка - ЛюбаяСсылка
//     * Наименование - Строка
//     * Код - Строка
//     * ПометкаУдаления - Булево
//   СтруктураЗапроса - см. ТекстЗапросаПоискаДублей
//
Процедура ДобавитьСтрокуКандидатов(КоллекцияДублей, ТаблицаКандидатов,  Знач ДанныеОсновногоЭлемента, 
	Знач ДанныеКандидата, Знач СтруктураЗапроса)
	
	Если КоллекцияДублей.НезначимыеДубли[ДанныеОсновногоЭлемента.Ссылка] <> Неопределено
		Или КоллекцияДублей.НезначимыеДубли[ДанныеКандидата.Ссылка] <> Неопределено Тогда
		Возврат;
	КонецЕсли;

	Строка = ТаблицаКандидатов.Добавить();
	Строка.ЭтоДубли = Ложь;
	Строка.Ссылка1  = ДанныеОсновногоЭлемента.Ссылка;
	Строка.Ссылка2  = ДанныеКандидата.Ссылка;
	
	Строка.Поля1 = Новый Структура("Код, Наименование, ПометкаУдаления", 
		ДанныеОсновногоЭлемента.Код, ДанныеОсновногоЭлемента.Наименование, ДанныеОсновногоЭлемента.ПометкаУдаления);
	Строка.Поля2 = Новый Структура("Код, Наименование, ПометкаУдаления", 
		ДанныеКандидата.Код, ДанныеКандидата.Наименование, ДанныеКандидата.ПометкаУдаления);
	
	Для Каждого ИмяПоля Из СтруктураЗапроса.ИменаПолейДляСравненияНаРавенство Цикл
		Строка.Поля1.Вставить(ИмяПоля, ДанныеОсновногоЭлемента[ИмяПоля]);
		Строка.Поля2.Вставить(ИмяПоля, ДанныеКандидата[ИмяПоля]);
	КонецЦикла;
	
	Для Каждого ИмяПоля Из СтруктураЗапроса.ИменаПолейДляСравненияНаПодобие Цикл
		Строка.Поля1.Вставить(ИмяПоля, ДанныеОсновногоЭлемента[ИмяПоля]);
		Строка.Поля2.Вставить(ИмяПоля, ДанныеКандидата[ИмяПоля]);
	КонецЦикла;
	
	Для Каждого КлючЗначение Из СтруктураЗапроса.РасшифровкаДополнительныхПолей Цикл
		ИмяКолонки = КлючЗначение.Значение;
		ИмяПоля    = КлючЗначение.Ключ;
		
		Строка.Поля1.Вставить(ИмяКолонки, ДанныеОсновногоЭлемента[ИмяПоля]);
		Строка.Поля2.Вставить(ИмяКолонки, ДанныеКандидата[ИмяПоля]);
	КонецЦикла;
	
КонецПроцедуры

Процедура ЗарегистрироватьДубль(КоллекцияДублей, Знач Элемент1, Знач Элемент2, Знач СтруктураЗапроса)
	
	ТаблицаДублей = КоллекцияДублей.ТаблицаДублей;
	НезначимыеДубли = КоллекцияДублей.НезначимыеДубли;
	// Определить, какой элемент уже является дублем.
	СтрокаДублей1 = ТаблицаДублей.Найти(Элемент1.Ссылка, "Ссылка");
	СтрокаДублей2 = ТаблицаДублей.Найти(Элемент2.Ссылка, "Ссылка");
	
	ЕстьНезначимыйДубль1 = КоллекцияДублей.НезначимыеДубли[Элемент1.Ссылка] <> Неопределено;
	ЕстьНезначимыйДубль2 = КоллекцияДублей.НезначимыеДубли[Элемент2.Ссылка] <> Неопределено;
	
	Дубль1Зарегистрирован = (СтрокаДублей1 <> Неопределено) Или ЕстьНезначимыйДубль1;
	Дубль2Зарегистрирован = (СтрокаДублей2 <> Неопределено) Или ЕстьНезначимыйДубль2;
	
	// Если оба элемента есть дубли, то ничего делать не надо.
	// Или если хотя бы один из элементов незначимый, то пара не является дублем.
	Если Дубль1Зарегистрирован И Дубль2Зарегистрирован
		Или ЕстьНезначимыйДубль1 Или ЕстьНезначимыйДубль2 Тогда
		Возврат;
	КонецЕсли;
	
	// Перед регистрацией дубля определить группу дублей.
	Если Дубль1Зарегистрирован Тогда
		СсылкаГруппыДублей = ?(ЗначениеЗаполнено(СтрокаДублей1.Родитель), СтрокаДублей1.Родитель, СтрокаДублей1.Ссылка);
	ИначеЕсли Дубль2Зарегистрирован Тогда
		СсылкаГруппыДублей = ?(ЗначениеЗаполнено(СтрокаДублей2.Родитель), СтрокаДублей2.Родитель, СтрокаДублей2.Ссылка);
	Иначе // Регистрация группы дублей.
		ГруппаДублей = ТаблицаДублей.Добавить();
		ГруппаДублей.Ссылка = Элемент1.Ссылка;
		СсылкаГруппыДублей = ГруппаДублей.Ссылка;
	КонецЕсли;
	
	СписокСвойств = "Ссылка,Код,Наименование,ПометкаУдаления," 
		+ СтрСоединить(СтруктураЗапроса.ИменаПолейДляСравненияНаРавенство, ",") + "," 
		+ СтрСоединить(СтруктураЗапроса.ИменаПолейДляСравненияНаПодобие, ",");
	
	Если Не Дубль1Зарегистрирован Тогда
			
		СведенияОДубле = ТаблицаДублей.Добавить();
		ЗаполнитьЗначенияСвойств(СведенияОДубле, Элемент1, СписокСвойств);
		СведенияОДубле.Родитель = СсылкаГруппыДублей;
		
		Если СведенияОДубле.ПометкаУдаления Тогда
			КоллекцияДублей.ДублиКПроверке.Добавить(СведенияОДубле.Ссылка);	
		КонецЕсли;
		
	КонецЕсли;
	
	Если Не Дубль2Зарегистрирован Тогда
			
		СведенияОДубле = ТаблицаДублей.Добавить();
		ЗаполнитьЗначенияСвойств(СведенияОДубле, Элемент2, СписокСвойств);
		СведенияОДубле.Родитель = СсылкаГруппыДублей;
		
		Если СведенияОДубле.ПометкаУдаления Тогда
			КоллекцияДублей.ДублиКПроверке.Добавить(СведенияОДубле.Ссылка);	
		КонецЕсли;
		
	КонецЕсли;
	
	Если КоллекцияДублей.ДублиКПроверке.Количество() >= КоллекцияДублей.КоличествоЭлементовДляСравнения Тогда
		УдалитьНезначимыеДубли(КоллекцияДублей);	
	КонецЕсли;
	
КонецПроцедуры

#Область Прочие

// Параметры:
//  ОбъектМетаданных - ОбъектМетаданных
// 
// Возвращаемое значение:
//  Структура:
//   * ДлинаКода - Число
//   * ДлинаНомера - Число
//   * ДлинаНаименования - Число
//   * Иерархический - Булево
//   * ВидИерархии - ВидИерархии
//   * ЕстьНаименование - Булево
//   * ЕстьКод - Булево
//   * ЕстьНомер - Булево
//
Функция ХарактеристикиОбъектаМетаданных(ОбъектМетаданных)
	
	Результат = Новый Структура;
	Результат.Вставить("ДлинаКода", 0);
	Результат.Вставить("ДлинаНомера", 0);
	Результат.Вставить("ДлинаНаименования", 0);
	Результат.Вставить("Иерархический", Ложь);
	Результат.Вставить("ВидИерархии", Неопределено);
	ЗаполнитьЗначенияСвойств(Результат, ОбъектМетаданных);
	Результат.Вставить("ЕстьНаименование", Результат.ДлинаНаименования > 0);
	Результат.Вставить("ЕстьКод", Результат.ДлинаКода > 0);
	Результат.Вставить("ЕстьНомер", Результат.ДлинаНомера > 0);
	Результат.Вставить("ПолноеИмяОбъектаМетаданных", ОбъектМетаданных.ПолноеИмя());
	Результат.Вставить("ОбъектМетаданных", ОбъектМетаданных);
	Возврат Результат;
	
КонецФункции

// Параметры:
//  ПараметрыПоиска - см. ПоискИУдалениеДублей.ПараметрыПоискаДублей
//  Характеристики - см. ХарактеристикиОбъектаМетаданных
// 
// Возвращаемое значение:
//  Структура:
//   * ТекстЗапроса - Строка
//   * РасшифровкаДополнительныхПолей - Соответствие
//   * ИменаПолейДляСравненияНаРавенство - Массив из Строка
//   * ИменаПолейДляСравненияНаПодобие - Массив из Строка
//   * ИменаПолейВВыборе - Массив из Строка
//
Функция ТекстЗапросаПоискаДублей(ПараметрыПоиска, Характеристики, ИменаДополнительныхПолей)
	
	ИменаПолейДляСравненияНаРавенство = Новый Массив; // Имена реквизитов, по которым сравниваем по равенству.
	ИменаПолейДляСравненияНаПодобие   = Новый Массив; // Имена реквизитов, по которым будем нечетко сравнивать.
	Для Каждого Строка Из ПараметрыПоиска.ПравилаПоиска Цикл
		Если Строка.Правило = "Равно" Тогда
			ИменаПолейДляСравненияНаРавенство.Добавить(Строка.Реквизит);
		ИначеЕсли Строка.Правило = "Подобно" Тогда
			ИменаПолейДляСравненияНаПодобие.Добавить(Строка.Реквизит);
		КонецЕсли
	КонецЦикла;
	
	// Задаем псевдонимы дополнительных полей, чтобы не пересекались с остальными полями.
	ИменаПолейВЗапросе = ДоступныеРеквизитыОтбора(Характеристики.ОбъектМетаданных);
	Если Не Характеристики.ЕстьКод Тогда
		Если Характеристики.ЕстьНомер Тогда 
			ИменаПолейВЗапросе = ИменаПолейВЗапросе + ", Номер КАК Код";
		Иначе
			ИменаПолейВЗапросе = ИменаПолейВЗапросе + ", НЕОПРЕДЕЛЕНО КАК Код";
		КонецЕсли;
	КонецЕсли;
	Если Не Характеристики.ЕстьНаименование Тогда
		ИменаПолейВЗапросе = ИменаПолейВЗапросе + ", Ссылка КАК Наименование";
	КонецЕсли;
	ИменаПолейВВыборе = ОбщегоНазначения.СкопироватьРекурсивно(ИменаПолейДляСравненияНаРавенство);
	ОбщегоНазначенияКлиентСервер.ДополнитьМассив(ИменаПолейВВыборе, ИменаПолейДляСравненияНаПодобие);
	
	РасшифровкаДополнительныхПолей = Новый Соответствие;
	ПорядковыйНомер = 0;
	Для Каждого ПолеТаблицы Из Новый Структура(ИменаДополнительныхПолей) Цикл
		ИмяПоля   = ПолеТаблицы.Ключ;
		Псевдоним = "Доп" + Формат(ПорядковыйНомер, "ЧН=; ЧГ=") + "_" + ИмяПоля;
		РасшифровкаДополнительныхПолей.Вставить(Псевдоним, ИмяПоля);
		
		ИменаПолейВЗапросе = ИменаПолейВЗапросе + "," + ИмяПоля + " КАК " + Псевдоним;
		ИменаПолейВВыборе.Добавить(Псевдоним);
		ПорядковыйНомер = ПорядковыйНомер + 1;
	КонецЦикла;
	
	ТекстЗапроса = "ВЫБРАТЬ РАЗРЕШЕННЫЕ * ИЗ #Таблица";
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "*", ИменаПолейВЗапросе);
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "#Таблица", Характеристики.ПолноеИмяОбъектаМетаданных);
	
	Результат = Новый Структура;
	Результат.Вставить("ТекстЗапроса", ТекстЗапроса);
	Результат.Вставить("РасшифровкаДополнительныхПолей", РасшифровкаДополнительныхПолей);
	Результат.Вставить("ИменаПолейДляСравненияНаРавенство", ИменаПолейДляСравненияНаРавенство);
	Результат.Вставить("ИменаПолейДляСравненияНаПодобие", ИменаПолейДляСравненияНаПодобие);
	Результат.Вставить("ИменаПолейВВыборе", ИменаПолейВВыборе);
	
	Возврат Результат;
	
КонецФункции

// Параметры:
//  ПараметрыПоиска - см. ПоискИУдалениеДублей.ПараметрыПоискаДублей
//  Характеристики - см. ХарактеристикиОбъектаМетаданных
//  СтруктураЗапроса - см. ТекстЗапросаПоискаДублей
//  ЭталонныйОбъект - ЛюбаяСсылка, СправочникОбъект
// 
// Возвращаемое значение:
//  Структура:
//   * СхемаКД - СхемаКомпоновкиДанных
//   * НастройкиКД - НастройкиКомпоновкиДанных
//   * ВыборкаЭталонныхОбъектов - Структура:
//     ** Таблица 
//     ** ТекущийЭлемент 
//     ** Индекс 
//     ** ВГраница 
//     ** ПроцессорКД 
//     ** ПроцессорВыводаКД 
//   * ОтборыКандидатов - Соответствие
//
Функция СхемаКомпоновкиДанныхПоискаДублей(ПараметрыПоиска, Характеристики, СтруктураЗапроса, ЭталонныйОбъект)
	
	СхемаКД = Новый СхемаКомпоновкиДанных;
	ИсточникДанныхСхемыКД = СхемаКД.ИсточникиДанных.Добавить();
	ИсточникДанныхСхемыКД.Имя = "ИсточникДанных1";
	ИсточникДанныхСхемыКД.ТипИсточникаДанных = "Local";
	
	НаборДанных = СхемаКД.НаборыДанных.Добавить(Тип("НаборДанныхЗапросСхемыКомпоновкиДанных"));
	НаборДанных.Имя = "НаборДанных1";
	НаборДанных.ИсточникДанных = "ИсточникДанных1";
	НаборДанных.Запрос = СтруктураЗапроса.ТекстЗапроса;
	НаборДанных.АвтоЗаполнениеДоступныхПолей = Истина;
	
	КомпоновщикНастроекКД = Новый КомпоновщикНастроекКомпоновкиДанных;
	КомпоновщикНастроекКД.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(СхемаКД));
	КомпоновщикНастроекКД.ЗагрузитьНастройки(ПараметрыПоиска.КомпоновщикПредварительногоОтбора.Настройки);
	НастройкиКД = КомпоновщикНастроекКД.Настройки;
	
	// Поля.
	НастройкиКД.Выбор.Элементы.Очистить();
	Для Каждого ИмяПоля Из СтруктураЗапроса.ИменаПолейВВыборе Цикл
		ПолеКД = Новый ПолеКомпоновкиДанных(СокрЛП(ИмяПоля));
		ДоступноеПолеКД = НастройкиКД.ДоступныеПоляВыбора.НайтиПоле(ПолеКД);
		Если ДоступноеПолеКД = Неопределено Тогда
			ЗаписьЖурналаРегистрации(ПоискИУдалениеДублей.НаименованиеПодсистемы(Ложь),
				УровеньЖурналаРегистрации.Предупреждение, Характеристики.ОбъектМетаданных, ЭталонныйОбъект,
				СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр("ru = 'Поле ""%1"" не существует.'"), Строка(ПолеКД)));
			Продолжить;
		КонецЕсли;
		ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
		ВыбранноеПолеКД.Поле = ПолеКД;
	КонецЦикла;
	ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
	ВыбранноеПолеКД.Поле = Новый ПолеКомпоновкиДанных("Ссылка");
	ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
	ВыбранноеПолеКД.Поле = Новый ПолеКомпоновкиДанных("Код");
	ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
	ВыбранноеПолеКД.Поле = Новый ПолеКомпоновкиДанных("Наименование");
	ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
	ВыбранноеПолеКД.Поле = Новый ПолеКомпоновкиДанных("ПометкаУдаления");
	Если Характеристики.Иерархический
			И Характеристики.ВидИерархии = Метаданные.СвойстваОбъектов.ВидИерархии.ИерархияГруппИЭлементов Тогда
		
		ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
		ВыбранноеПолеКД.Поле = Новый ПолеКомпоновкиДанных("ЭтоГруппа");
		
		ВыбранноеПолеКД = НастройкиКД.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
		ВыбранноеПолеКД.Поле = Новый ПолеКомпоновкиДанных("Родитель");
	КонецЕсли;
	
	// Сортировки.
	НастройкиКД.Порядок.Элементы.Очистить();
	ЭлементПорядкаКД = НастройкиКД.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
	ЭлементПорядкаКД.Поле = Новый ПолеКомпоновкиДанных("Ссылка");
	
	// Отборы.
	Если Характеристики.ОбъектМетаданных = Метаданные.Справочники.Пользователи Тогда
		ЭлементОтбораКД = НастройкиКД.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбораКД.ЛевоеЗначение  = Новый ПолеКомпоновкиДанных("Служебный");
		ЭлементОтбораКД.ВидСравнения   = ВидСравненияКомпоновкиДанных.Равно;
		ЭлементОтбораКД.ПравоеЗначение = Ложь;
	КонецЕсли;
	
	// Структура.
	НастройкиКД.Структура.Очистить();
	ГруппировкаКД = НастройкиКД.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
	ГруппировкаКД.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
	ГруппировкаКД.Порядок.Элементы.Добавить(Тип("АвтоЭлементПорядкаКомпоновкиДанных"));
	
	// Чтение данных оригиналов.
	Если ЭталонныйОбъект = Неопределено Тогда
		ВыборкаЭталонныхОбъектов = ИнициализироватьВыборкуКД(СхемаКД, КомпоновщикНастроекКД.ПолучитьНастройки());
	Иначе
		ТаблицаЗначений = ОбъектВТаблицуЗначений(ЭталонныйОбъект, СтруктураЗапроса.РасшифровкаДополнительныхПолей);
		Если Не Характеристики.ЕстьКод И Не Характеристики.ЕстьНомер Тогда
			ТаблицаЗначений.Колонки.Добавить("Код", Новый ОписаниеТипов("Неопределено"));
		КонецЕсли;
		ВыборкаЭталонныхОбъектов = ИнициализироватьВыборкуТЗ(ТаблицаЗначений);
	КонецЕсли;
	
	// Подготовка СКД к чтению данных дублей.
	ОтборыКандидатов = Новый Соответствие;
	Для Каждого ИмяПоля Из СтруктураЗапроса.ИменаПолейДляСравненияНаРавенство Цикл
		ИмяПоля = СокрЛП(ИмяПоля);
		ЭлементОтбораКД = НастройкиКД.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбораКД.ЛевоеЗначение = Новый ПолеКомпоновкиДанных(ИмяПоля);
		ЭлементОтбораКД.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
		ОтборыКандидатов.Вставить(ИмяПоля, ЭлементОтбораКД);
	КонецЦикла;
	ЭлементОтбораКД = НастройкиКД.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ЭлементОтбораКД.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
	ЭлементОтбораКД.ВидСравнения = ?(ЭталонныйОбъект = Неопределено, ВидСравненияКомпоновкиДанных.Больше, 
		ВидСравненияКомпоновкиДанных.НеРавно);
	ОтборыКандидатов.Вставить("Ссылка", ЭлементОтбораКД);
	
	Если Характеристики.Иерархический
			И Характеристики.ВидИерархии = Метаданные.СвойстваОбъектов.ВидИерархии.ИерархияГруппИЭлементов Тогда
		
		ЭлементОтбораКД = НастройкиКД.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбораКД.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЭтоГруппа");
		ЭлементОтбораКД.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
		ОтборыКандидатов.Вставить("ЭтоГруппа", ЭлементОтбораКД);
		
		ГруппаИЛИ = НастройкиКД.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
		ГруппаИЛИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
		
		ГруппаИ = ГруппаИЛИ.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
		ГруппаИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;
		
		ЭлементОтбораКД = ГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбораКД.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЭтоГруппа");
		ЭлементОтбораКД.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
		ЭлементОтбораКД.ПравоеЗначение = Ложь;
		
		ГруппаИ = ГруппаИЛИ.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
		ГруппаИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;
		
		ЭлементОтбораКД = ГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбораКД.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЭтоГруппа");
		ЭлементОтбораКД.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
		ЭлементОтбораКД.ПравоеЗначение = Истина;
		
		ЭлементОтбораКД = ГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
		ЭлементОтбораКД.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Родитель");
		ЭлементОтбораКД.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
		ОтборыКандидатов.Вставить("Родитель", ЭлементОтбораКД);
	КонецЕсли;
	
	Результат = Новый Структура;
	Результат.Вставить("СхемаКД", СхемаКД);
	Результат.Вставить("НастройкиКД", НастройкиКД);
	Результат.Вставить("ВыборкаЭталонныхОбъектов", ВыборкаЭталонныхОбъектов);
	Результат.Вставить("ОтборыКандидатов", ОтборыКандидатов);
	Возврат Результат;

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

// Параметры:
//  СхемаКД - СхемаКомпоновкиДанных
//  НастройкиКД - НастройкиКомпоновкиДанных
//
// Возвращаемое значение:
//  Структура:
//    * Таблица - ТаблицаЗначений:
//        ** Индекс - Число
//        ** ВГраница - Число
//    * ТекущийЭлемент - СтрокаТаблицыЗначений:
//        ** Ссылка - ЛюбаяСсылка
//    * Индекс - Число
//    * ВГраница - Число
//    * ПроцессорКД - ПроцессорКомпоновкиДанных
//    * ПроцессорВыводаКД - ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений
// 
Функция ИнициализироватьВыборкуКД(СхемаКД, НастройкиКД)
	Выборка = Новый Структура("Таблица, ТекущийЭлемент, Индекс, ВГраница, ПроцессорКД, ПроцессорВыводаКД");
	КомпоновщикМакетаКД = Новый КомпоновщикМакетаКомпоновкиДанных;
	МакетКД = КомпоновщикМакетаКД.Выполнить(СхемаКД, НастройкиКД, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
	
	Выборка.ПроцессорКД = Новый ПроцессорКомпоновкиДанных;
	Выборка.ПроцессорКД.Инициализировать(МакетКД);
	
	Выборка.Таблица = Новый ТаблицаЗначений;
	Выборка.Индекс = -1;
	Выборка.ВГраница = -100;
	
	Выборка.ПроцессорВыводаКД = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
	Выборка.ПроцессорВыводаКД.УстановитьОбъект(Выборка.Таблица);
	
	Возврат Выборка;
КонецФункции

Функция ИнициализироватьВыборкуТЗ(ТаблицаЗначений)
	Выборка = Новый Структура("Таблица, ТекущийЭлемент, Индекс, ВГраница, ПроцессорКД, ПроцессорВыводаКД");
	Выборка.Таблица = ТаблицаЗначений;
	Выборка.Индекс = -1;
	Выборка.ВГраница = ТаблицаЗначений.Количество() - 1;
	Возврат Выборка;
КонецФункции

Функция СледующийЭлементВыборки(Выборка)
	Если Выборка.Индекс >= Выборка.ВГраница Тогда
		Если Выборка.ПроцессорКД = Неопределено Тогда
			Возврат Ложь;
		КонецЕсли;
		Если Выборка.ВГраница = -100 Тогда
			Выборка.ПроцессорВыводаКД.НачатьВывод();
		КонецЕсли;
		Выборка.Таблица.Очистить();
		Выборка.Индекс = -1;
		Выборка.ВГраница = -1;
		Пока Выборка.ВГраница = -1 Цикл
			ЭлементРезультатаКД = Выборка.ПроцессорКД.Следующий();
			Если ЭлементРезультатаКД = Неопределено Тогда
				Выборка.ПроцессорВыводаКД.ЗакончитьВывод();
				Возврат Ложь;
			КонецЕсли;
			Выборка.ПроцессорВыводаКД.ВывестиЭлемент(ЭлементРезультатаКД);
			Выборка.ВГраница = Выборка.Таблица.Количество() - 1;
		КонецЦикла;
	КонецЕсли;
	Выборка.Индекс = Выборка.Индекс + 1;
	Выборка.ТекущийЭлемент = Выборка.Таблица[Выборка.Индекс];
	Возврат Истина;
КонецФункции

// Удаляет дубли из коллекции, если дубль помечен на удаление и не имеет мест использования.
Процедура УдалитьНезначимыеДубли(КоллекцияДублей)
	
	// Не удаляем незначимые дубли, т.к. нет ограничения
	Если НЕ КоллекцияДублей.СкрыватьНезначимыеДубли Тогда
		Возврат;
	КонецЕсли;
	
	ТаблицаДублей = КоллекцияДублей.ТаблицаДублей;
	ДублиКПроверке = КоллекцияДублей.ДублиКПроверке;
	 
	// Не будем проверять дубли, т.к. набрали нужное количество для вывода результата 
	Если ТаблицаДублей.Количество() - ДублиКПроверке.Количество() <= КоллекцияДублей.КоличествоЭлементовДляСравнения Тогда
		
		МестаИспользования = ОбщегоНазначения.МестаИспользования(ДублиКПроверке);
		МестаИспользования.Индексы.Добавить("Ссылка, ВспомогательныеДанные");
		ОтборДублей = Новый Структура("Ссылка", Неопределено);
		ОтборМестИспользования = Новый Структура("Ссылка, ВспомогательныеДанные", Неопределено, Ложь);
		 
		Для Индекс = 0 По ДублиКПроверке.ВГраница() Цикл
			
			ОтборДублей.Ссылка = ДублиКПроверке[Индекс];
			ПроверяемыеДубли = ТаблицаДублей.НайтиСтроки(ОтборДублей);
			Для каждого Дубль Из ПроверяемыеДубли Цикл
				
				Если Дубль.Родитель = Неопределено Тогда
					Продолжить;
				КонецЕсли;
				
				ОтборМестИспользования.Ссылка = Дубль.Ссылка;
				МестаИспользованияВнутренний = МестаИспользования.НайтиСтроки(ОтборМестИспользования);
				Если МестаИспользованияВнутренний.Количество() = 0 И Дубль.ПометкаУдаления Тогда
					КоллекцияДублей.НезначимыеДубли[Дубль.Ссылка] = Истина;
					КоллекцияДублей.НезначимыеДубли[Дубль.Родитель] = Истина;
					КоллекцияДублей.ОбработанныеГруппы.Вставить(Дубль.Родитель);
					ТаблицаДублей.Удалить(Дубль);
				КонецЕсли;
				
			КонецЦикла;
		КонецЦикла;
				
	КонецЕсли;
	
	ДублиКПроверке.Очистить();

КонецПроцедуры

// Удаляет группы и ее элементы, если в группе только один элемент.
Процедура УдалитьНезначимыеГруппы(КоллекцияДублей)

	ТаблицаДублей = КоллекцияДублей.ТаблицаДублей;
	ОтборДублей = Новый Структура("Родитель", Неопределено);
	Для каждого ГруппаДублей Из КоллекцияДублей.ОбработанныеГруппы Цикл
		
		ОтборДублей.Родитель = ГруппаДублей.Ключ;
		Дубли = ТаблицаДублей.НайтиСтроки(ОтборДублей);
		Если Дубли.Количество() = 1 Тогда
			ТаблицаДублей.Удалить(Дубли[0]);
		КонецЕсли;
		
		Группа = ТаблицаДублей.Найти(ГруппаДублей.Ключ, "Ссылка");
		Если Группа <> Неопределено Тогда
			ТаблицаДублей.Удалить(Группа);
		КонецЕсли;
		
	КонецЦикла;
	
КонецПроцедуры

// Возвращаемое значение:
//   ТаблицаЗначений:
//     * Ссылка - ЛюбаяСсылка 
//     * Родитель - ЛюбаяСсылка 
//     * Наименование - Строка 
//     * ЭтоГруппа - Булево 
//     * ПометкаУдаления - Булево 
//
Функция ТаблицаДублей(ИменаПолейДляСравненияНаПодобие, ИменаПолейДляСравненияНаРавенство)
	
	ТаблицаДублей = Новый ТаблицаЗначений;
	ТаблицаДублей.Колонки.Добавить("Ссылка");
	Для Каждого ПолеТаблицы Из ИменаПолейДляСравненияНаРавенство Цикл
		Если ТаблицаДублей.Колонки.Найти(ПолеТаблицы) = Неопределено Тогда
			ТаблицаДублей.Колонки.Добавить(ПолеТаблицы);
		КонецЕсли;
	КонецЦикла;
	
	Для Каждого ПолеТаблицы Из ИменаПолейДляСравненияНаПодобие Цикл
		Если ТаблицаДублей.Колонки.Найти(ПолеТаблицы) = Неопределено Тогда
			ТаблицаДублей.Колонки.Добавить(ПолеТаблицы);
		КонецЕсли;
	КонецЦикла;
	
	ОбязательныеКолонки = СтрРазделить("Код,Наименование,Родитель,ПометкаУдаления,ЭтоГруппа", ",");
	Для Каждого ИмяПоляТаблицы Из ОбязательныеКолонки Цикл
		Если ТаблицаДублей.Колонки.Найти(ИмяПоляТаблицы) = Неопределено Тогда
			ТаблицаДублей.Колонки.Добавить(ИмяПоляТаблицы);
		КонецЕсли;
	КонецЦикла;
	
	ТаблицаДублей.Индексы.Добавить("Ссылка");
	ТаблицаДублей.Индексы.Добавить("Родитель");
	ТаблицаДублей.Индексы.Добавить("Ссылка,Родитель,ЭтоГруппа");
	
	Возврат ТаблицаДублей;
КонецФункции

// Возвращаемое значение:
//   Структура:
//     * НезначимыеДубли - Соответствие из КлючИЗначение:
//       ** Ключ - ЛюбаяСсылка
//       ** Значение - Булево
//     * ОбработанныеГруппы - Соответствие
//     * СкрыватьНезначимыеДубли - Булево
//     * ДублиКПроверке - Массив
//     * КоличествоЭлементовДляСравнения - Произвольный
//                                       - Неопределено
//     * ТаблицаДублей - см. ТаблицаДублей
//
Функция КоллекцияДублей(Знач ПараметрыПоиска, ИменаПолейДляСравненияНаПодобие, ИменаПолейДляСравненияНаРавенство)
	
	КоллекцияДублей = Новый Структура;
	КоллекцияДублей.Вставить("ТаблицаДублей", ТаблицаДублей(ИменаПолейДляСравненияНаПодобие, ИменаПолейДляСравненияНаРавенство));
	КоллекцияДублей.Вставить("КоличествоЭлементовДляСравнения", 
		ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(ПараметрыПоиска, "МаксимальноеЧислоДублей", 0));
	КоллекцияДублей.Вставить("ДублиКПроверке", Новый Массив);
	
	КоллекцияДублей.Вставить("СкрыватьНезначимыеДубли", Ложь);
	Если ПараметрыПоиска.Свойство("СкрыватьНезначимыеДубли")
		И ПараметрыПоиска.СкрыватьНезначимыеДубли = Истина
			ИЛИ КоллекцияДублей.КоличествоЭлементовДляСравнения = 0 Тогда
	
		КоллекцияДублей.СкрыватьНезначимыеДубли = Истина;
	КонецЕсли;
	 
	КоллекцияДублей.Вставить("ОбработанныеГруппы", Новый Соответствие);	
	КоллекцияДублей.Вставить("НезначимыеДубли", Новый Соответствие);

	Возврат КоллекцияДублей;

КонецФункции

// Возвращаемое значение:
//   ТаблицаЗначений:
//     * Ссылка1 - ЛюбаяСсылка
//     * Поля1 - Структура:
//       ** Наименование - Строка
//       ** Код - Строка
//     * Ссылка2 - ЛюбаяСсылка
//     * Поля2 - Структура:
//       ** Наименование - Строка
//       ** Код - Строка
//     * ЭтоДубли - Булево
//
Функция ТаблицаКандидатов()
	ТаблицаКандидатов = Новый ТаблицаЗначений;
	КолонкиКандидатов = ТаблицаКандидатов.Колонки;
	КолонкиКандидатов.Добавить("Ссылка1");
	КолонкиКандидатов.Добавить("Поля1");
	КолонкиКандидатов.Добавить("Ссылка2");
	КолонкиКандидатов.Добавить("Поля2");
	КолонкиКандидатов.Добавить("ЭтоДубли", Новый ОписаниеТипов("Булево"));
	ТаблицаКандидатов.Индексы.Добавить("ЭтоДубли");
	Возврат ТаблицаКандидатов;

КонецФункции

#КонецОбласти

#КонецОбласти

#Иначе
ВызватьИсключение НСтр("ru = 'Недопустимый вызов объекта на клиенте.'");
#КонецЕсли

Связанные материалы