При интеграции со сторонними системами или миграции исторических данных программистам 1С часто приходится работать с файлами формата DBF. Одной из типовых задач при такой обработке является поиск дубликатов. Если файл содержит десятки тысяч записей, простой перебор строк в цикле с поиском каждого значения "в лоб" займет огромное количество времени. Стандартные методы Найти и НайтиПоКлючу объекта XBase используют индексный файл, но возвращают только первое найденное совпадение, игнорируя последующие дубликаты.
В этой статье мы подробно разберем несколько эффективных способов решения этой задачи средствами 1С (для версий 7.7 и 8.х), а также проанализируем альтернативные варианты решения проблемы вне платформы.
---
Этот способ является наиболее простым в реализации и отлично подходит для разовых задач, когда объем данных не превышает нескольких сотен тысяч строк (для 50 000 записей этот метод отработает практически мгновенно).
Суть метода: мы считываем данные из DBF-файла во временную таблицу значений в памяти 1С. Затем добавляем вспомогательную числовую колонку, заполняем ее единицами, группируем таблицу по проверяемому полю и суммируем единицы. Строки, где сумма получилась больше единицы, и есть наши дубликаты.
Разберем этот алгоритм по шагам на примере кода для 1С 8.х:
// Создаем объект для работы с DBF
ФайлДБФ = Новый XBase;
ФайлДБФ.ОткрытьФайлы("C:\Data\clients.dbf");
// Создаем Таблицу Значений для анализа
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("КодКлиента", Новый ОписаниеТипов("Строка"));
ТЗ.Колонки.Добавить("Счетчик", Новый ОписаниеТипов("Число"));
// Загружаем данные из DBF в ТЗ
Если ФайлДБФ.Открыта() Тогда
ФайлДБФ.Первая();
Пока Не ФайлДБФ.В Конце() Цикл
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.КодКлиента = СокрЛП(ФайлДБФ.CLIENT_ID);
НоваяСтрока.Счетчик = 1; // Заполняем вспомогательную колонку
ФайлДБФ.Следующая();
КонецЦикла;
ФайлДБФ.ЗакрытьФайлы();
КонецЕсли;
// Сворачиваем таблицу по анализируемому полю, суммируя счетчик
ТЗ.Свернуть("КодКлиента", "Счетчик");
// Сортируем по убыванию счетчика, чтобы дубли оказались вверху
ТЗ.Сортировать("Счетчик УБЫВ");
// Выводим результаты анализа
Для Каждого СтрокаТЗ Из ТЗ Цикл
Если СтрокаТЗ.Счетчик > 1 Тогда
Сообщить("Значение '" + СтрокаТЗ.КодКлиента + "' повторяется " + СтрокаТЗ.Счетчик + " раз(а).");
Иначе
// Так как таблица отсортирована, при первом значении <= 1 можно прервать цикл
Прервать;
КонецЕсли;
КонецЦикла;
Преимущества: простота написания кода, отсутствие необходимости создавать внешние индексы, высокая скорость работы на средних объемах данных.
Недостатки: вся таблица загружается в оперативную память. Если в DBF-файле очень много полей и миллионы записей, это может привести к избыточному потреблению ресурсов.
---
Если мы не хотим расходовать оперативную память на создание больших коллекций значений в 1С, мы можем задействовать штатный механизм индексации объекта XBase. Данный метод работает непосредственно с файловой структурой DBF.
Суть метода: если для DBF-файла создан индекс по искомому полю, то все идентичные записи в физическом файле при чтении по этому индексу будут идти строго друг за другом. Мы позиционируемся на первом вхождении ключа, а затем в цикле читаем последующие записи, пока значение ключевого поля совпадает с искомым.
Проанализируем пример реализации такого обхода:
ФайлДБФ = Новый XBase;
// Открываем файл вместе с индексным файлом (CDX или NDX)
ФайлДБФ.ОткрытьФайлы("C:\Data\clients.dbf", "C:\Data\clients.cdx");
// Устанавливаем активный индекс (например, по полю CLIENT_ID)
ФайлДБФ.ТекущийИндекс = ФайлДБФ.Индексы.ID_INDEX;
// Определяем искомое значение
ИскомыйКлюч = "12345";
// Находим первую запись по ключу
Если ФайлДБФ.НайтиПоКлючу(ИскомыйКлюч, "=") Тогда
КоличествоДублей = 0;
// Читаем подряд все записи, соответствующие нашему ключу
Пока СокрЛП(ФайлДБФ.CLIENT_ID) = ИскомыйКлюч И Не ФайлДБФ.В Конце() Цикл
КоличествоДублей = КоличествоДублей + 1;
// Здесь можно обработать дублирующуюся строку (например, вывести в лог или пометить на удаление)
Сообщить("Найдена запись: " + ФайлДБФ.CLIENT_NAME);
ФайлДБФ.Следующая();
КонецЦикла;
Если КоличествоДублей > 1 Тогда
Сообщить("Всего найдено дубликатов для ключа " + ИскомыйКлюч + ": " + КоличествоДублей);
КонецЕсли;
Иначе
Сообщить("Записи с таким ключом не найдены.");
EndIf;
ФайлДБФ.ЗакрытьФайлы();
Преимущества: минимальное потребление оперативной памяти, высокая скорость позиционирования на диске благодаря использованию индексов.
Недостатки: требуется наличие корректного и актуального индексного файла (CDX/NDX). Если файл поврежден, результаты поиска будут неверными.
---
Для разработчиков на управляемой платформе 1С 8.3 доступен продвинутый инструмент — использование языка запросов 1С для анализа таблиц в памяти. Это гораздо гибче, чем обычный метод Свернуть, так как позволяет использовать условия связи и получать детальные строки.
Рассмотрим, как с помощью временных таблиц найти не просто количество дублей, а извлечь из исходного DBF все строки, которые дублируются, со всеми сопутствующими реквизитами:
// Предположим, что ТЗ_Исходная уже заполнена данными из DBF-файла
// (как показано в первом методе)
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Передаем заполненную таблицу значений в параметры запроса
Запрос.УстановитьПараметр("ТаблицаДБФ", ТЗ_Исходная);
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаДБФ.КодКлиента КАК КодКлиента,
| ТаблицаДБФ.ИмяКлиента КАК ИмяКлиента,
| ТаблицаДБФ.Адрес КАК Адрес
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТаблицаДБФ КАК ТаблицаДБФ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Шаг 1: Находим только те ключи, которые дублируются
|ВЫБРАТЬ
| ВТ_ИсходныеДанные.КодКлиента КАК КодКлиента,
| КОЛИЧЕСТВО(ВТ_ИсходныеДанные.КодКлиента) КАК Количество
|ПОМЕСТИТЬ ВТ_ДублирующиесяКлючи
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
|
|СГРУППИРОВАТЬ ПО
| ВТ_ИсходныеДанные.КодКлиента
|
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(ВТ_ИсходныеДанные.КодКлиента) > 1
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Шаг 2: Соединяем исходные данные с дублями, чтобы получить полную информацию
|ВЫБРАТЬ
| Исходные.КодКлиента КАК КодКлиента,
| Исходные.ИмяКлиента КАК ИмяКлиента,
| Исходные.Адрес КАК Адрес
|ИЗ
| ВТ_ИсходныеДанные КАК Исходные
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ДублирующиесяКлючи КАК Дубли
| ПО Исходные.КодКлиента = Дубли.КодКлиента
|ОБЪЕДИНИТЬ ВСЕ
|СОРТИРОВАТЬ ПО
| КодКлиента";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующая() Цикл
// Мы получили полную информацию по каждой дублирующейся строке
Сообщить("Дубликат: " + Выборка.КодКлиента + " | " + Выборка.ИмяКлиента);
КонецЦикла;
Преимущества: мощный инструмент анализа. С помощью SQL-подобного синтаксиса 1С можно накладывать сложные условия отбора дубликатов по нескольким полям одновременно.
---
Этот метод по праву считается самым быстрым при работе в среде Windows. Мы не загружаем данные в память 1С построчно и не используем внутренние механизмы платформы для перебора. Вместо этого мы делегируем задачу поиска дубликатов драйверу базы данных (например, Microsoft Jet OLEDB или VFPOLEDB).
Выясним, как составить и выполнить такой запрос:
// Определяем путь к каталогу с файлом (в ADO каталогом является сама "База Данных")
КаталогФайла = "C:\Data\";
ИмяФайлаDBF = "clients.dbf";
Connection = Новый COMОбъект("ADODB.Connection");
Recordset = Новый COMОбъект("ADODB.Recordset");
// Строка подключения для драйвера Visual FoxPro (рекомендуется для DBF)
ConnectionString = "Provider=VFPOLEDB.1;Data Source=" + КаталогФайла + ";Mode=ReadWrite;Collating Sequence=MACHINE";
Попытка
Connection.Open(ConnectionString);
Исключение
Сообщить("Не удалось подключиться к базе данных DBF через ADO: " + ОписаниеОшибки());
Возврат;
КонецПопытки;
// Составляем классический SQL-запрос для поиска дубликатов
ТекстЗапроса = "SELECT CLIENT_ID, COUNT(*) as cnt FROM " + ИмяФайлаDBF + " GROUP BY CLIENT_ID HAVING COUNT(*) > 1";
Попытка
Recordset.Open(ТекстЗапроса, Connection, 3, 1); // adOpenStatic, adLockReadOnly
Исключение
Сообщить("Ошибка выполнения запроса ADO: " + ОписаниеОшибки());
Connection.Close();
Возврат;
КонецПопытки;
// Считываем результаты напрямую в 1С
Пока Recordset.EOF() = 0 Цикл
КодКлиента = Recordset.Fields("CLIENT_ID").Value;
Количество = Recordset.Fields("cnt").Value;
Сообщить("Значение '" + СокрЛП(КодКлиента) + "' дублируется. Найдено строк: " + Количество);
Recordset.MoveNext();
КонецЦикла;
Recordset.Close();
Connection.Close();
Преимущества: колоссальная скорость выполнения запроса. Драйвер базы данных оптимизирует выполнение операции группировки на низком уровне, минуя трансляцию объектов через программный интерфейс 1С.
Недостатки: требуется наличие зарегистрированного в операционной системе провайдера данных (драйвера OLEDB). В современных 64-битных операционных системах иногда требуется ручная настройка и установка соответствующих драйверов.
---
Если перед вами стоит исключительно разовая задача по очистке или анализу конкретного файла DBF, совсем не обязательно писать код внутри конфигурации 1С. Рассмотрим два простых и доступных инструмента:
Если размер DBF-файла не превышает лимиты Excel (1 048 576 строк), файл можно легко открыть непосредственно в Excel. Далее, перейдя на вкладку "Данные", вы можете импортировать файл в редактор Power Query, выделить нужный столбец, кликнуть по нему правой кнопкой мыши и выбрать инструмент "Удалить дубликаты" (или предварительно сгруппировать строки по ключевому столбцу с операцией подсчета строк, чтобы увидеть статистику по дубликатам).
Эти программные продукты разработаны специально для администрирования файлов dBase. В них по умолчанию встроены графические мастера для работы с дублями. Достаточно открыть файл, выбрать в меню пункт "Инструменты" -> "Поиск дубликатов", отметить галочками ключевые поля, после чего утилита мгновенно выделит цветом, отфильтрует или удалит лишние строки.
Выбор конкретного метода зависит от регулярности выполнения задачи, требований к автоматизации процесса и квалификации разработчика. Для регулярных интеграционных процедур в коде 1С мы рекомендуем использовать комбинацию загрузки в ТаблицуЗначений для небольших файлов или прямой SQL-запрос через ADO для работы с базами данных объемом в миллионы строк.