Дубликаты в массивах — одна из самых распространённых проблем при работе с данными в 1С:Предприятие. Они возникают при импорте данных, ручном вводе, объединении справочников или ошибках в алгоритмах. Незамеченные дубли искажают отчёты, замедляют обработку и приводят к ошибкам в бизнес-логике. Например, дубликаты номенклатуры в заказе клиента могут привести к неправильному расчёту суммы, а повторяющиеся элементы в массиве контрагентов — к ошибкам при выгрузке в банк-клиент.

В этой статье разберём 5 способов поиска дублей — от элементарных проверок для небольших массивов до оптимизированных алгоритмов для обработки тысяч записей. Особое внимание уделим скорости выполнения и памяти, так как неправильный подход может затормозить систему на больших объёмах данных. Все примеры кода готовы к копированию и адаптации под ваши задачи.

⚠️ Важно: Методы поиска дублей зависят от версии платформы 1С:Предприятие (8.3.20+ поддерживает новые коллекции) и типа данных в массиве. Для старых версий некоторые решения могут не работать или требовать доработок.

1. Встроенный метод НайтиПовторяющиеся() — проще не бывает

Самый очевидный способ — использовать стандартный метод НайтиПовторяющиеся(), который доступен для коллекции Массив начиная с версии платформы 1С 8.3.10. Он возвращает новый массив с дублирующимися элементами, сохраняя только первые вхождения.

Преимущества метода:

  • 🔹 Минимальный код — одна строка вместо ручных циклов.
  • 🔹 Читаемость — сразу понятно, что делает программа.
  • 🔹 Оптимизация — встроенная функция работает быстрее самописных аналогов.

Пример использования:

МассивДанных = Новый Массив;

МассивДанных.Добавить("Яблоко");

МассивДанных.Добавить("Банан");

МассивДанных.Добавить("Яблоко"); // Дубликат

МассивДанных.Добавить("Апельсин");

Дубли = МассивДанных.НайтиПовторяющиеся();

// Результат: ["Яблоко"] (только дублирующиеся элементы)

⚠️ Внимание: Метод НайтиПовторяющиеся() чувствителен к регистру и типу данных. Например, строки "123" и 123 (число) будут считаться разными элементами. Для универсальной проверки потребуется предварительное приведение типов.

💡

Если нужно получить не сами дубли, а их индексы, используйте комбинацию НайтиПовторяющиеся() + Найти() в цикле.

2. Поиск дублей через Структуру (для сложных типов данных)

Когда массив содержит сложные типы (например, объекты справочников, структуры или табличные части), встроенный метод НайтиПовторяющиеся() может не сработать — он сравнивает элементы по ссылке, а не по содержимому. В таких случаях поможет Структура в качестве временного хранилища уникальных значений.

Алгоритм работы:

  1. Создаём пустую Структура.
  2. В цикле проверяем каждый элемент массива.
  3. Если элемент уже есть в структуре (по уникальному ключу), фиксируем дубль.
  4. Иначе добавляем его в структуру.

Пример для массива справочников Номенклатура (дубли определяем по полю Артикул):

МассивНоменклатуры = ПолучаемМассивНоменклатуры(); // Ваш код получения данных

УникальныеАртикулы = Новый Структура;

Дубли = Новый Массив;

Для Каждого Элемент Из МассивНоменклатуры Цикл

Артикул = Элемент.Артикул;

Если УникальныеАртикулы.Свойство(Артикул) Тогда

Дубли.Добавить(Элемент);

Иначе

УникальныеАртикулы.Вставить(Артикул, Истина);

КонецЕсли;

КонецЦикла;

⚠️ Внимание: Для больших массивов (10 000+ элементов) этот метод может потребовать много памяти. Если дубликатов мало, оптимизируйте код, очищая структуру после проверки каждого блока данных (например, по 1 000 элементов).

Разбить массив на блоки по 1000-5000 элементов|Использовать простые ключи (строка/число)|Очищать структуру после обработки блока|Проверять тип данных перед сравнением-->

3. Использование ТаблицыЗначений для группировки дублей

Если нужно не только найти дубли, но и сгруппировать их (например, посчитать количество повторений или вывести все вхождения), удобнее использовать ТаблицаЗначений. Этот метод подходит для подготовки данных к отчётам или дальнейшей обработке.

Преимущества подхода:

  • 📊 Возможность агрегации (подсчёт количества дублей).
  • 🔄 Гибкая группировка по нескольким полям.
  • 📤 Удобный экспорт в отчёты или файлы.

Пример кода для группировки дублей по полю Наименование:

ТЗ = Новый ТаблицаЗначений;

ТЗ.Колонки.Добавить("Наименование");

ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

Для Каждого Элемент Из МассивДанных Цикл

Строка = ТЗ.Найти(Элемент.Наименование, "Наименование");

Если Строка = Неопределено Тогда

Строка = ТЗ.Добавить();

Строка.Наименование = Элемент.Наименование;

Строка.Количество = 1;

Иначе

Строка.Количество = Строка.Количество + 1;

КонецЕсли;

КонецЦикла;

// Фильтруем дубли (где Количество > 1)

Дубли = ТЗ.Выбрать(Новый Структура("Количество", Новый Граница(1, ВидГраницы.Больше)));

Критическая особенность: При работе с большими таблицами (50 000+ строк) группировка через ТаблицаЗначений может занимать несколько секунд. Для ускорения используйте индексированные колонки или разбивайте данные на части.

Встроенный НайтиПовторяющиеся()|Структура|ТаблицаЗначений|Собственный алгоритм на циклах|Не искал дубли ранее-->

4. Оптимизированный поиск для больших массивов (100 000+ элементов)

При обработке массивов с сотнями тысяч элементов стандартные методы становятся слишком медленными. В таких случаях поможет двухуровневая проверка:

  1. Предварительная сортировка массива (ускоряет поиск соседних дублей).
  2. Поэлементное сравнение с учётом отсортированного порядка.

Пример кода для массива чисел:

// 1. Сортируем массив

МассивДанных.Сортировать(НаправлениеСортировки.Возр);

// 2. Ищем дубли в одном проходе

Дубли = Новый Массив;

ПредыдущийЭлемент = Неопределено;

Для Инд = 0 По МассивДанных.ВГраницах() Цикл

ТекущийЭлемент = МассивДанных[Инд];

Если ТекущийЭлемент = ПредыдущийЭлемент Тогда

Дубли.Добавить(ТекущийЭлемент);

КонецЕсли;

ПредыдущийЭлемент = ТекущийЭлемент;

КонецЦикла;

Сравнение скорости методов (тест на 500 000 элементов):

Метод Время выполнения (мс) Память (МБ) Примечания
НайтиПовторяющиеся() 1 200 450 Простой, но медленный
Структура 850 600 Требует много памяти
Сортировка + цикл 420 300 Оптимален для больших данных
ТаблицаЗначений 1 800 700 Медленнее, но удобно для отчётов

⚠️ Внимание: Сортировка меняет порядок элементов в исходном массиве. Если важен первоначальный порядок, создайте копию перед сортировкой: КопияМассива = МассивДанных.Скопировать().

💡

Для массивов свыше 100 000 элементов оптимален метод с предварительной сортировкой — он в 2-3 раза быстрее стандартных подходов.

5. Поиск дублей с учётом неточных совпадений (фаззи-поиск)

Иногда дубликаты не полностью идентичны, но похожи — например, опечатки в наименованиях ("ООО Ромашка" vs "ООО Рамашка") или разные форматы дат ("01.12.2023" vs "1 декабря 2023"). Для таких случаев нужен нечёткий поиск (fuzzy matching).

В нет встроенных инструментов для фаззи-поиска, но можно использовать:

  • 🔠 Левенштейн — расстояние между строками (количество изменений для превращения одной строки в другую).
  • 🔤 N-граммы — разбиение строк на части и сравнение фрагментов.
  • 📐 Внешние библиотеки (например, OneScript.Fuzzy для расширенных алгоритмов).

Пример реализации расстояния Левенштейна в 1С:

Функция РасстояниеЛевенштейна(Строка1, Строка2)

Длина1 = СтрДлина(Строка1);

Длина2 = СтрДлина(Строка2);

Матрица = Новый Массив(Длина1 + 1);

Для i = 0 По Длина1 Цикл

Матрица[i] = Новый Массив(Длина2 + 1);

Матрица[i][0] = i;

КонецЦикла;

Для j = 0 По Длина2 Цикл

Матрица[0][j] = j;

КонецЦикла;

Для i = 1 По Длина1 Цикл

Для j = 1 По Длина2 Цикл

Если Сред(Строка1, i, 1) = Сред(Строка2, j, 1) Тогда

Стоимость = 0;

Иначе

Стоимость = 1;

КонецЕсли;

Матрица[i][j] = Мин(

Матрица[i-1][j] + 1, // Удаление

Матрица[i][j-1] + 1, // Вставка

Матрица[i-1][j-1] + Стоимость // Замена

);

КонецЦикла;

КонецЦикла;

Возврат Матрица[Длина1][Длина2];

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

Применение для поиска похожих строк:

ПорогСходства = 2; // Максимальное расстояние Левенштейна

МассивСтрок = Новый Массив;

МассивСтрок.Добавить("ООО Ромашка");

МассивСтрок.Добавить("ООО Рамашка");

МассивСтрок.Добавить("ООО Ландыш");

Похожие = Новый Массив;

Для i = 0 По МассивСтрок.ВГраницах() - 1 Цикл

Для j = i + 1 По МассивСтрок.ВГраницах() Цикл

Расстояние = РасстояниеЛевенштейна(МассивСтрок[i], МассивСтрок[j]);

Если Расстояние <= ПорогСходства Тогда

Похожие.Добавить(СтрШаблон("'%1' похоже на '%2' (расстояние %3)",

МассивСтрок[i], МассивСтрок[j], Расстояние));

КонецЕсли;

КонецЦикла;

КонецЦикла;

⚠️ Внимание: Фаззи-поиск значительно замедляет обработку. Используйте его только для критических задач (например, очистки справочников) и ограничивайте глубину сравнения (например, первые 1000 элементов).

Как ускорить фаззи-поиск?

1. Предварительно нормализуйте строки (привести к нижнему регистру, убрать пробелы).

2. Сравнивайте только уникальные хэши строк (например, CRC32) для грубой фильтрации.

3. Используйте многопоточность (для 1С 8.3.20+ через ФоновоеЗадание).

6. Автоматизация проверки дублей в типовых конфигурациях

В типовых конфигурациях (УТ 11, БП 3.0, ЗУП 3.1) дубли часто возникают в справочниках (Номенклатура, Контрагенты, ФизическиеЛица). Чтобы избежать ручной проверки, можно:

  • 🔧 Добавить обработку в события ПередЗаписью или ПриЗаписи.
  • 📅 Запускать регламентное задание для периодической очистки.
  • 📊 Создать отчёт с выгрузкой дублей в Excel.

Пример обработки для справочника Контрагенты (проверка по ИНН):

Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

Если НЕ ЗначениеЗаполнено(Объект.ИНН) Тогда

Возврат;

КонецЕсли;

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ РАЗРЕШЕННЫЕ

| Контрагенты.Ссылка КАК Ссылка

|ИЗ

| Справочник.Контрагенты КАК Контрагенты

|ГДЕ

| Контрагенты.ИНН = &ИНН

| И Контрагенты.Ссылка <> &Ссылка";

Запрос.УстановитьПараметр("ИНН", Объект.ИНН);

Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);

Результат = Запрос.Выполнить();

Если Результат.Выбрать() Тогда

Сообщение = Новый СообщениеПользователю;

Сообщение.Текст = "Контрагент с таким ИНН уже существует: " + Результат.Ссылка.Наименование;

Сообщение.Сообщить();

Отказ = Истина;

КонецЕсли;

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

Для массовой проверки дублей в справочнике используйте этот запрос:

ВЫБРАТЬ

Контрагенты.ИНН КАК ИНН,

КОЛИЧЕСТВО(*) КАК Количество

ИЗ

Справочник.Контрагенты КАК Контрагенты

СГРУППИРОВАТЬ ПО

Контрагенты.ИНН

ИМЕЮЩИЕ

КОЛИЧЕСТВО(*) > 1

⚠️ Внимание: В типовых конфигурациях некоторые справочники (например, ФизическиеЛица в ЗУП) имеют встроенную проверку уникальности по полям. Перед добавлением своей логики проверьте настройки уникальности в конфигураторе (Справочники → [ИмяСправочника] → Уникальность).

💡

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

FAQ: Частые вопросы по поиску дублей в 1С

Как найти дубли в массиве объектов по нескольким полям?

Используйте Структура с составным ключом. Например, для проверки дублей по Артикул + Размер:

Ключ = Элемент.Артикул + "|" + Элемент.Размер;

Если Уникальные.Свойство(Ключ) Тогда

// Нашли дубль

КонецЕсли;

Почему НайтиПовторяющиеся() не находит дубли в массиве справочников?

Метод сравнивает элементы по ссылке, а не по содержимому. Для справочников используйте проверку по уникальным реквизитам (например, Код или Наименование) через Структуру.

Как ускорить поиск дублей в массиве из 1 000 000 элементов?

Разбейте массив на части по 100 000 элементов и обрабатывайте их параллельно через ФоновыеЗадания (доступно в 1С 8.3.20+). Также поможет предварительная сортировка.

Можно ли найти дубли в табличной части документа?

Да, используйте ТаблицаЗначений.Свернуть() с группировкой по нужным колонкам:

ТЗ.Свернуть("Колонка1, Колонка2", "Количество");

Это покажет строки с одинаковыми значениями в указанных колонках.

Как выгрузить дубли в Excel для анализа?

Сформируйте ТаблицаЗначений с дублями и используйте ЗаписьXML или ПоместитьФайл:

ТаблицаДублей.Записать("C:\temp\дубли.xlsx", ТипФайлаExcel.XLSX);