При работе с крупными справочниками (от сотен тысяч позиций) разработчики часто сталкиваются с задачей поиска дубликатов по наименованию. Попытка решить эту проблему «в лоб» через создание неоптимизированных временных таблиц и сложное соединение справочника с самим собой нередко приводит к аварийному завершению работы 1С с сообщением о нехватке памяти. Рассмотрим, почему падает производительность исходного алгоритма, и разберем по шагам правильные и быстрые варианты поиска дублей.
Проанализируем фрагмент запроса, вызывающий перегрузку сервера или клиента:
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.НаименованиеПолное
ПОМЕСТИТЬ ВТТабл
ИЗ
Справочник.Номенклатура КАК Номенклатура
;
ВЫБРАТЬ
ВТТабл.НаименованиеПолное,
ВТТабл.Ссылка
ИЗ
ВТТабл КАК ВТТабл
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
ПО ВТТабл.Ссылка.Код <> Номенклатура.Код
И (Номенклатура.НаименованиеПолное = ВТТабл.НаименованиеПолное)
И ВТТабл.Ссылка.Наименование <> Номенклатура.Наименование
Выясним главные причины критического потребления ресурсов:
ВТТабл.Ссылка.Код и ВТТабл.Ссылка.Наименование заставляют платформу 1С генерировать дополнительные скрытые соединения LEFT JOIN к исходной таблице справочника, многократно усложняя план выполнения.ПО. Использование оператора <> не позволяет СУБД применить эффективные алгоритмы слияния (такие как Hash Match или Merge Join). СУБД вынуждена использовать вложенные циклы (Nested Loops) или формировать огромное декартово произведение, что моментально забивает базу tempdb и оперативную память.НаименованиеПолное имеет тип неограниченной длины, СУБД тратит дополнительные ресурсы на операции сравнения, а в некоторых случаях платформа вовсе блокирует прямое сравнение и группировку без явного приведения типов.Самый простой, быстрый и эффективный способ получить список значений, которые повторяются более одного раза, — выполнить группировку непосредственно по наименованию и отсечь уникальные позиции условием ИМЕЮЩИЕ КОЛИЧЕСТВО(...) > 1. Рассмотрим этот пример:
ВЫБРАТЬ
Номенклатура.Наименование КАК Наименование,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК КоличествоДублей
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
И НЕ Номенклатура.ПометкаУдаления
СГРУППИРОВАТЬ ПО
Номенклатура.Наименование
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) > 1
Разберем важные нюансы этого запроса:
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка), чтобы точно исключить возможные коллизии.ЭтоГруппа = ЛОЖЬ), так как дубли среди папок справочника обычно не являются предметом анализа.НаименованиеПолное неограниченной длины, его необходимо ограничить с помощью функции языка запросов ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(1000)).Часто требуется не просто увидеть наименования-дубликаты, но и получить все конкретные ссылки на позиции номенклатуры для последующей обработки или вывода в детальный отчет. Для этого построим пакетный запрос с индексированной временной таблицей:
ВЫБРАТЬ
ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(500)) КАК НаименованиеПолное
ПОМЕСТИТЬ ВТ_ДублиНаименований
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ Номенклатура.ЭтоГруппа
СГРУППИРОВАТЬ ПО
ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(500))
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) > 1
ИНДЕКСИРОВАТЬ ПО
НаименованиеПолное
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
Номенклатура.Код КАК Код,
Номенклатура.Наименование КАК Наименование,
ВТ_ДублиНаименований.НаименованиеПолное КАК НаименованиеПолное
ИЗ
ВТ_ДублиНаименований КАК ВТ_ДублиНаименований
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
ПО ВТ_ДублиНаименований.НаименованиеПолное = ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(500))
ГДЕ
НЕ Номенклатура.ЭтоГруппа
УПОРЯДОЧИТЬ ПО
ВТ_ДублиНаименований.НаименованиеПолное,
Номенклатура.Код
Обратим внимание на ключевые особенности этого решения:
ИНДЕКСИРОВАТЬ ПО НаименованиеПолное гарантирует, что СУБД построит индекс в базе tempdb, и второе соединение выполнится практически мгновенно без повторного полного сканирования таблицы.ПО используется строго знак =. Никаких проверок на неравенство кодов или наименований здесь быть не должно. Все элементы, попавшие во внутреннее соединение с предварительно отобранным списком, по определению являются дублями.Если задача заключается в программной обработке найденных дублей, избегайте выгрузки всего результата в память через Запрос.Выполнить().Выгрузить(). Это создает огромную нагрузку на оперативную память клиентского или серверного сеанса. Используем потоковую выборку:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка каждой записи по отдельности
КонецЦикла;
Для экранного отчета наилучшим инструментом является Система компоновки данных (СКД). СКД самостоятельно оптимизирует постраничный вывод, эффективно использует оперативную память и позволяет пользователю гибко настраивать группировки и отборы.
Если конфигурация разработана на базе Библиотеки стандартных подсистем (БСП) — например, «Управление торговлей», «ERP», «Комплексная автоматизация» или «1С:Бухгалтерия», — писать собственные отчеты и запросы с нуля часто не требуется.
В состав БСП входит обработка «Поиск и удаление дублирующихся элементов» (или «Поиск и замена дублей»). Рассмотрим ее преимущества:
ЗаменаСсылок, что защищает информационную базу от появления битых ссылок.