Top.Mail.Ru

Проверка на наличие дублирующихся строк в табличных частях

Рассмотрен вопрос осуществления проверки на наличие дублирующихся строк в табличных частях, предложен альтернативный вариант.

Думаю, каждый программист рано или поздно сталкивался с подобной задачей: как реализовать проверку на наличие дублирующихся строк в табличных частях. Кому-то просто нужно проверить: есть, или нет, дубли. Кому-то нужно известить пользователя, и сообщить ему номера строк с дублями. Это вопросы будут рассмотрены в данной статье. Но давайте сразу определимся с терминологией: поля, по которым будет осуществляться контроль, назовем «ключевые».

Итак, предположим, что у нас стоит задача проверить наличие дублей строк табличной части по ключевым полям. Пусть это будет документ «Реализация товаров, услуг», дубли строк мы будем искать в табличной части «Товары», а в качестве ключевых полей будем использовать следующие реквизиты табличной части: Качество, Номенклатура, Склад, СерияНоменклатуры и ХарактеристикаНоменклатуры.

Рассмотрим следующие ситуации:

1) Проверка на наличие дублирующихся строк на уровне есть/нет. Реализация ее будет выглядеть следующим образом:

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

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

2) Классика жанра. Выведем в сообщении пользователю номера всех тех строк табличной части, которые являются дублями.

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.Текст =
"ВЫБРАТЬ
|	ТабЧасть.Качество,
|	ТабЧасть.Номенклатура,
|	ТабЧасть.СерияНоменклатуры,
|	ТабЧасть.Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры,
|	ТабЧасть.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_ТабЧасть
|ИЗ
|	Документ.РеализацияТоваровУслуг.Товары КАК ТабЧасть
|ГДЕ
|	ТабЧасть.Ссылка = &Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ТабЧасть.Качество КАК Качество,
|	ТабЧасть.Номенклатура КАК Номенклатура,
|	ТабЧасть.СерияНоменклатуры КАК СерияНоменклатуры,
|	ТабЧасть.Склад КАК Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
|	ТабЧасть.НомерСтроки КАК НомерСтроки
|ИЗ
|	ВТ_ТабЧасть КАК ТабЧасть
|ГДЕ
|	(ТабЧасть.Качество, ТабЧасть.Номенклатура, ТабЧасть.СерияНоменклатуры, ТабЧасть.Склад, ТабЧасть.ХарактеристикаНоменклатуры) В
|			(ВЫБРАТЬ
|				ВТ.Качество,
|				ВТ.Номенклатура,
|				ВТ.СерияНоменклатуры,
|				ВТ.Склад,
|				ВТ.ХарактеристикаНоменклатуры
|			ИЗ
|				ВТ_ТабЧасть КАК ВТ
|			СГРУППИРОВАТЬ ПО
|				ВТ.Качество,
|				ВТ.Номенклатура,
|				ВТ.СерияНоменклатуры,
|				ВТ.Склад,
|				ВТ.ХарактеристикаНоменклатуры
|			ИМЕЮЩИЕ
|				КОЛИЧЕСТВО(ВТ.НомерСтроки) > 1)
|
|УПОРЯДОЧИТЬ ПО
|	НомерСтроки
|ИТОГИ ПО
|	Качество,
|	Номенклатура,
|	СерияНоменклатуры,
|	Склад";
РезЗапроса = Запрос.Выполнить();
Если Не РезЗапроса.Пустой() Тогда
	Выб_Качество = РезЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
	Пока Выб_Качество.Следующий() Цикл
		Выб_Номенклатура = Выб_Качество.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
		Пока Выб_Номенклатура.Следующий() Цикл
			Выб_СерияНоменклатуры = Выб_Номенклатура.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
			Пока Выб_СерияНоменклатуры.Следующий() Цикл
				Выб_Склад = Выб_СерияНоменклатуры.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
				Пока Выб_Склад.Следующий() Цикл
					Выборка = Выб_Склад.Выбрать();
					
					ТекстСообщения = "";
					
					Пока Выборка.Следующий() Цикл
						ТекстСообщения = ТекстСообщения + ?(ПустаяСтрока(ТекстСообщения), "", ", ") + Выборка.НомерСтроки;
					КонецЦикла;
					
					ТекстСообщения = "Обнаружено дублирование строк: " + ТекстСообщения;
					Сообщить(ТекстСообщения);
				КонецЦикла;
			КонецЦикла;
		КонецЦикла;
	КонецЦикла;
КонецЕсли;

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

А теперь попробуем оценить перспективу доработки. Допустим, у нас изменился состав ключевых полей в сторону увеличения их количества: добавились ЕдиницаИзмерения и ЗаказПокупателя. Чтобы контроль дублей строк не перестал работать, нам нужно доработать запрос и обход результата запроса следующим образом:

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Запрос.Текст =
"ВЫБРАТЬ
|	ТабЧасть.ЕдиницаИзмерения,
|	ТабЧасть.ЗаказПокупателя,
|	ТабЧасть.Качество,
|	ТабЧасть.Номенклатура,
|	ТабЧасть.СерияНоменклатуры,
|	ТабЧасть.Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры,
|	ТабЧасть.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_ТабЧасть
|ИЗ
|	Документ.РеализацияТоваровУслуг.Товары КАК ТабЧасть
|ГДЕ
|	ТабЧасть.Ссылка = &Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ТабЧасть.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
|	ТабЧасть.ЗаказПокупателя КАК ЗаказПокупателя,
|	ТабЧасть.Качество КАК Качество,
|	ТабЧасть.Номенклатура КАК Номенклатура,
|	ТабЧасть.СерияНоменклатуры КАК СерияНоменклатуры,
|	ТабЧасть.Склад КАК Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
|	ТабЧасть.НомерСтроки КАК НомерСтроки
|ИЗ
|	ВТ_ТабЧасть КАК ТабЧасть
|ГДЕ
|	(ТабЧасть.ЕдиницаИзмерения, ТабЧасть.ЗаказПокупателя, ТабЧасть.Качество, ТабЧасть.Номенклатура, ТабЧасть.СерияНоменклатуры, ТабЧасть.Склад, ТабЧасть.ХарактеристикаНоменклатуры) В
|			(ВЫБРАТЬ
|				ВТ.ЕдиницаИзмерения,
|				ВТ.ЗаказПокупателя,
|				ВТ.Качество,
|				ВТ.Номенклатура,
|				ВТ.СерияНоменклатуры,
|				ВТ.Склад,
|				ВТ.ХарактеристикаНоменклатуры
|			ИЗ
|				ВТ_ТабЧасть КАК ВТ
|			СГРУППИРОВАТЬ ПО
|				ВТ.ЕдиницаИзмерения,
|				ВТ.ЗаказПокупателя,
|				ВТ.Качество,
|				ВТ.Номенклатура,
|				ВТ.СерияНоменклатуры,
|				ВТ.Склад,
|				ВТ.ХарактеристикаНоменклатуры
|			ИМЕЮЩИЕ
|				КОЛИЧЕСТВО(ВТ.НомерСтроки) > 1)
|
|УПОРЯДОЧИТЬ ПО
|	НомерСтроки
|ИТОГИ ПО
|	Качество,
|	Номенклатура,
|	СерияНоменклатуры,
|	Склад,
|	ХарактеристикаНоменклатуры,
|	ЕдиницаИзмерения";
РезЗапроса = Запрос.Выполнить();
Если Не РезЗапроса.Пустой() Тогда
	Выб_Качество = РезЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
	Пока Выб_Качество.Следующий() Цикл
		Выб_Номенклатура = Выб_Качество.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
		Пока Выб_Номенклатура.Следующий() Цикл
			Выб_СерияНоменклатуры = Выб_Номенклатура.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
			Пока Выб_СерияНоменклатуры.Следующий() Цикл
				Выб_Склад = Выб_СерияНоменклатуры.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
				Пока Выб_Склад.Следующий() Цикл
					Выб_ХарактеристикаНоменклатуры = Выб_Склад.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
					Пока Выб_ХарактеристикаНоменклатуры.Следующий() Цикл
						Выб_ЕдиницаИзмерения = Выб_ХарактеристикаНоменклатуры.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
						Пока Выб_ЕдиницаИзмерения.Следующий()  Цикл
							ТекстСообщения = "";
							
							Выборка = Выб_ЕдиницаИзмерения.Выбрать();
							Пока Выборка.Следующий() Цикл
								ТекстСообщения = ТекстСообщения + ?(ПустаяСтрока(ТекстСообщения), "", ", ") + Выборка.НомерСтроки;
							КонецЦикла;
							
							ТекстСообщения = "Обнаружено дублирование строк: " + ТекстСообщения;
							Сообщить(ТекстСообщения);
						КонецЦикла;
					КонецЦикла;
				КонецЦикла;
			КонецЦикла;
		КонецЦикла;
	КонецЦикла;
КонецЕсли;

3) Альтернативный вариант. Реализация предыдущего варианта другим способом. Предлагаю использовать особенность запросов 1С, позволяющих обращаться к данным табличных частей, как к обычным полям выборки. Ну все-таки не совсем обычным, но все же полям выборки. Кроме того, нам придётся использовать не самый оптимальный способ соединения данных - декартово произведение. Почему так - опишу ниже.

ШаблонОшибки = "Табличная часть 'Товары': по реквизитам 'Качество, Номенклатура, СерияНоменклатуры, Склад, ХарактеристикаНоменклатуры' обнаружено дублирование строк '%1'";
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
Запрос.Текст =
"ВЫБРАТЬ
|	ТабЧасть.Ссылка,
|	МИНИМУМ(ТабЧасть.НомерСтроки) КАК МинНомерСтроки,
|	ТабЧасть.Качество,
|	ТабЧасть.Номенклатура,
|	ТабЧасть.СерияНоменклатуры,
|	ТабЧасть.Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры
|ПОМЕСТИТЬ ВТ_ТабЧасть
|ИЗ
|	Документ.РеализацияТоваровУслуг.Товары КАК ТабЧасть
|ГДЕ
|	ТабЧасть.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
|	ТабЧасть.Ссылка,
|	ТабЧасть.Качество,
|	ТабЧасть.Номенклатура,
|	ТабЧасть.СерияНоменклатуры,
|	ТабЧасть.Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры
|
|ИМЕЮЩИЕ
|	КОЛИЧЕСТВО(ТабЧасть.НомерСтроки) > 1
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ТабЧасть.Ссылка,
|	ТабЧасть.Качество,
|	ТабЧасть.Номенклатура,
|	ТабЧасть.СерияНоменклатуры,
|	ТабЧасть.Склад,
|	ТабЧасть.ХарактеристикаНоменклатуры,
|	Док.Товары.(
|		НомерСтроки,
|		Качество,
|		Номенклатура,
|		СерияНоменклатуры,
|		Склад,
|		ХарактеристикаНоменклатуры
|	) КАК ДублиСтрок
|ИЗ
|	(ВЫБРАТЬ
|		Док.Товары.(
|			НомерСтроки,
|			Качество,
|			Номенклатура,
|			СерияНоменклатуры,
|			Склад,
|			ХарактеристикаНоменклатуры
|		) КАК Товары
|	ИЗ
|		Документ.РеализацияТоваровУслуг КАК Док
|	ГДЕ
|		Док.Ссылка = &Ссылка
|		И (Док.Товары.Качество, Док.Товары.Номенклатура, Док.Товары.СерияНоменклатуры, Док.Товары.Склад, Док.Товары.ХарактеристикаНоменклатуры) В
|				(ВЫБРАТЬ
|					ВТ.Качество,
|					ВТ.Номенклатура,
|					ВТ.СерияНоменклатуры,
|					ВТ.Склад,
|					ВТ.ХарактеристикаНоменклатуры
|				ИЗ
|					ВТ_ТабЧасть КАК ВТ)) КАК Док
|	ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ТабЧасть КАК ТабЧасть
|	ПО (ИСТИНА)
|ГДЕ
|	Док.Товары.Качество = ТабЧасть.Качество
|	И Док.Товары.Номенклатура = ТабЧасть.Номенклатура
|	И Док.Товары.СерияНоменклатуры = ТабЧасть.СерияНоменклатуры
|	И Док.Товары.Склад = ТабЧасть.Склад
|	И Док.Товары.ХарактеристикаНоменклатуры = ТабЧасть.ХарактеристикаНоменклатуры
|
|УПОРЯДОЧИТЬ ПО
|	ТабЧасть.МинНомерСтроки,
|	Док.Товары.НомерСтроки";
ТЗ_Результат = Запрос.Выполнить().Выгрузить();
Для Каждого СтрТЗ Из ТЗ_Результат Цикл
	Сообщить(СтрШаблон(ШаблонОшибки, СтрСоединить(СтрТЗ.ДублиСтрок.ВыгрузитьКолонку("НомерСтроки"), ", ")));
КонецЦикла;

Главное отличие от предыдущего варианта: обход результата запроса осуществляется линейным способом. Т.е. при изменении состава ключевых полей меняется только текст запроса.

Несколько комментариев по поводу запроса.

Работа с табличными частями в качестве полей выборки накладывает ряд ограничений на выполнение запросов. Во-первых, это работа с временными таблицами. Т.е. нельзя помещать такие объекты во временные таблицы, но никто не мешает использовать вложенные запросы. Во-вторых, это соединения таблиц. Мне требовалось написать такой запрос, который бы возвратил мне наборы ключевых полей по которым имеются дубли, и многострочный объект, содержащий все строки с таким же набором ключевых полей. Обычные соединения (ВНУТРЕННЕЕ, ЛЕВОЕ, ПРАВОЕ, ПОЛНОЕ) возвращают всю табличную часть, что, в общем, правильно – для части объекта условие соединения же выполняется? Выполняется. Ну тогда и получите всю табличную часть. Искомый результат, как оказалось, достигается декартовым произведением. Мне как-то претит видеть в тексте запроса перечисление таблиц через запятую, поэтому я все декартовы произведения всегда реализую через «... СОЕДИНЕНИЕ ... ПО (ИСТИНА)». Чтобы ограничить мсье Декарта в аппетитах (и повысить производительность), применяются дополнительные ограничения.

Теперь о производительности. Производились тестовые замеры каждого из вариантов на документе с большим количеством строк в табличной части. Количество строк в тестируемом документе: 47 817, 4 комбинации ключевых полей с дублями по 2, 2, 3 и 4 строки. Результаты замеров:

Вариант 1) 0:00:00.078 сек.

Вариант 2) 0:00:00.265 сек.

Вариант 3) 0:00:01.513 сек.

Как видим, третий вариант, как и ожидалось, самый медленный, но он же является самым удобным в перспективе модификации.

 

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

r_1.jpg

1) Выбираем вид объекта: Документ или Справочник.

2) Выбираем тип объекта: Какой именно документ или справочник.

3) Выбираем табличную часть объекта.

4) Определяем состав ключевых полей в специальном диалоге

r_2.png

5) Если мы хотим, то можем указать ссылку на объект, чтобы проверить его на наличие дублей.

6) Если активен флажок «Сгенерировать и показать код для проверки на дубли», то будет сгенерирован программный код для выполнения проверки на дубли строк с имеющимися настройками.


Источник // infostart.ru