При работе с большими базами данных 1С:Предприятие часто возникает задача поиска и обработки дублирующихся элементов в справочниках. Например, когда необходимо найти приборы учета с одинаковыми заводскими номерами среди десятков тысяч записей. В этой статье мы подробно разберем несколько подходов к решению этой задачи: от простых циклов до высокопроизводительных запросов, а также рассмотрим профессиональные практики оптимизации.
Рассмотрим стандартную ситуацию: у нас есть ТаблицаЗначений (ТЗ), содержащая данные справочника. Самый очевидный, но при этом самый медленный способ — использование вложенного цикла. Проанализируем типичную ошибку начинающего разработчика:
// Ошибочный вариант
Пока Счетчик < ТЗ.Количество() Цикл
СчетчикВнутр = 0;
Пока СчетчикВнутр < ТЗ.Количество() Цикл
Если ТЗ[Счетчик].ЗаводскойНомер = ТЗ[СчетчикВнутр].ЗаводскойНомер Тогда
// Проблема: здесь элемент сравнивается сам с собой
НоваяСтрока = Объект.УдвоенныеПриборыУчета.Добавить();
// Заполнение данных...
КонецЕсли;
СчетчикВнутр = СчетчикВнутр + 1;
КонецЦикла;
Счетчик = Счетчик + 1;
КонецЦикла;
Главная проблема этого кода заключается в том, что условие ТЗ[Счетчик].ЗаводскойНомер = ТЗ[СчетчикВнутр].ЗаводскойНомер всегда будет истинным, когда индексы совпадут. В результате в список дублей попадут абсолютно все элементы справочника. Чтобы исправить эту ситуацию, необходимо добавить проверку на несовпадение индексов:
Если Счетчик <> СчетчикВнутр И ТЗ[Счетчик].ЗаводскойНомер = ТЗ[СчетчикВнутр].ЗаводскойНомер Тогда
// Теперь мы нашли реальный дубль
КонецЕсли;
Важное замечание: Алгоритм с вложенными циклами имеет сложность O(n²). Для 46 000 элементов системе придется выполнить более 2 миллиардов операций сравнения. Это крайне неэффективно и может привести к зависанию сеанса.
Разберем гораздо более быстрый алгоритм, который требует всего одного прохода по данным. Идея заключается в предварительной сортировке данных. Если отсортировать таблицу по полю ЗаводскойНомер, то все дубликаты окажутся в списке друг за другом.
Проанализируем шаги реализации:
ТаблицаЗначений.ТЗ.Сортировать("ЗаводскойНомер");.
ТЗ.Сортировать("ЗаводскойНомер");
Для Счетчик = 1 По ТЗ.Количество() - 1 Цикл
Если ТЗ[Счетчик].ЗаводскойНомер = ТЗ[Счетчик - 1].ЗаводскойНомер Тогда
// Найден дубль!
// Нужно дополнительно проверять, не выводили ли мы этот номер ранее
// если дублей больше двух подряд.
КонецЕсли;
КонецЦикла;
Этот метод работает значительно быстрее, так как основное время тратится на сортировку (O(n log n)), а последующий обход выполняется за один цикл.
Выясним, как решить задачу на уровне СУБД. Это самый правильный подход с точки зрения производительности в 1С. Для создания и отладки таких алгоритмов удобно использовать консоль запросов для управляемых форм 8.3. Мы разделим задачу на три этапа, используя временные таблицы.
Шаг 1. Поиск дублирующихся номеров. Нам нужно найти те заводские номера, которые встречаются более одного раза.
ВЫБРАТЬ
ЗаводскойНомер
ПОМЕСТИТЬ ВТ_ДубликатыНомеров
ИЗ
Справочник.ПриборыУчета
СГРУППИРОВАТЬ ПО
ЗаводскойНомер
ИМЕЮЩИЕ
КОЛИЧЕСТВО(Ссылка) > 1
;
Шаг 2. Получение полной информации. Теперь выберем все записи справочника, чьи номера попали в список дублей. Если требуется обрабатывать огромные массивы данных порционно, можно применить специальный редактор запросов.
ВЫБРАТЬ
Приборы.Ссылка,
Приборы.Код,
Приборы.ЗаводскойНомер,
Приборы.ЛицевойСчет
ИЗ
Справочник.ПриборыУчета КАК Приборы
ГДЕ
Приборы.ЗаводскойНомер В (ВЫБРАТЬ ЗаводскойНомер ИЗ ВТ_ДубликатыНомеров)
УПОРЯДОЧИТЬ ПО
Приборы.ЗаводскойНомер
Такой запрос мгновенно вернет все группы дублей. Мы можем использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ вместо условия В (ВЫБРАТЬ...) для еще большей оптимизации на некоторых СУБД.
Если нам необходимо найти дубликаты программно при обработке внешних данных (например, загрузка из Excel), рассмотрим использование объекта Соответствие. Поиск по ключу в нем работает практически мгновенно.
КэшНомеров = Новый Соответствие;
Для Каждого СтрокаДанных Из ВходящиеДанные Цикл
Если КэшНомеров[СтрокаДанных.ЗаводскойНомер] <> Неопределено Тогда
// Мы уже встречали этот номер - это дубль!
Сообщить("Найден дубль: " + СтрокаДанных.ЗаводскойНомер);
Иначе
КэшНомеров.Вставить(СтрокаДанных.ЗаводскойНомер, Истина);
КонецЕсли;
КонецЦикла;
Проанализируем несколько важных моментов, которые часто упускают при поиске дублей:
ЗаводскойНомер <> "" в запросах или коде.СокрЛП() перед сравнением или очищать данные при записи.ЗаводскойНомер работал быстро на 46 000+ записях, убедитесь, что в метаданных справочника для этого реквизита установлено свойство Индексировать.Подводя итог, можно сказать: для быстрого поиска внутри базы всегда используйте Запросы. Если данные находятся в оперативной памяти (в таблице значений) — используйте Сортировку или Соответствие. Никогда не используйте вложенные циклы для больших объемов данных.