В процессе сопровождения баз данных на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с необходимостью наведения порядка в справочниках. Одной из типичных задач является поиск элементов, у которых значения определенных реквизитов (например, штрихкод, артикул или порядковый номер) дублируются. В этой статье мы подробно разберем несколько эффективных методов решения этой задачи, учитывая особенности работы с базами в формате DBF и специфику "черных запросов" семерки.
Стандартный запрос 1С 7.7 (так называемый "черный запрос") имеет свои ограничения. Главное из них заключается в том, что конструкция Условие накладывается на записи до того, как они будут сгруппированы. Поэтому мы не можем написать в тексте запроса аналог SQL-команды HAVING COUNT(*) > 1. Однако мы можем реализовать фильтрацию дублей при обходе результатов запроса.
Рассмотрим алгоритм действий. Нам необходимо сгруппировать данные сначала по проверяемому реквизиту, а затем по самому элементу справочника. Используя встроенную функцию Счётчик(), мы сможем определить количество вхождений каждого значения.
Проанализируем пример кода для поиска дублей по реквизиту ШтрихКод в справочнике ТМЦ:
Процедура ПоискДублейЗапросом()
Перем Запрос, ТекстЗапроса, Таб;
Запрос = СоздатьОбъект("Запрос");
// Формируем текст запроса.
// Группировка по реквизиту позволит собрать одинаковые значения вместе.
ТекстЗапроса =
"//{{ЗАПРОС(Дубли)
|ТекущийЭлемент = Справочник.ТМЦ.ТекущийЭлемент;
|ШтрихКод = Справочник.ТМЦ.ШтрихКод;
|Функция Колич = Счётчик();
|Группировка ШтрихКод;
|Группировка ТекущийЭлемент без групп;
|"//}}ЗАПРОС
;
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
Пока Запрос.Группировка(1) = 1 Цикл
// Здесь ключевой момент: если счетчик равен 1, значит значение уникально
Если Запрос.Колич <= 1 Тогда
Продолжить;
КонецЕсли;
// Если мы здесь, значит Запрос.ШтрихКод дублируется
Сообщить("Найден дубль значения: " + Запрос.ШтрихКод + " (встречается " + Запрос.Колич + " раз)");
// Обходим сами элементы, которые имеют этот дублирующийся реквизит
Пока Запрос.Группировка(2) = 1 Цикл
Сообщить(" --- Элемент: " + Запрос.ТекущийЭлемент);
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Этот метод надежен и работает "из коробки" на любой конфигурации. Однако при очень больших объемах данных (свыше 100 000 элементов) он может работать медленно, так как запрос сначала выбирает абсолютно все записи справочника.
Если справочник содержит десятки тысяч элементов (например, 70 000, как в обсуждаемом случае), иногда быстрее работает механизм манипуляции с Таблицей Значений. Мы можем использовать метод ВыгрузитьИтоги или просто заполнить таблицу в цикле, а затем применить встроенный метод Свернуть.
Рассмотрим по шагам этот способ:
ТаблицаЗначений.ЗначениеРеквизита), колонку для ссылки Элемент и числовую колонку Колво.ЗначениеРеквизита, суммируя колонку Колво.Пример реализации:
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Значение", "Строка");
ТЗ.НоваяКолонка("Элемент");
ТЗ.НоваяКолонка("Колво", "Число");
Спр = СоздатьОбъект("Справочник.ТМЦ");
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
ТЗ.НоваяСтрока();
ТЗ.Значение = Спр.ШтрихКод;
ТЗ.Элемент = Спр.ТекущийЭлемент();
ТЗ.Колво = 1;
КонецЦикла;
// Копируем ТЗ, чтобы сохранить ссылки на элементы
ТЗ_Дубли = СоздатьОбъект("ТаблицаЗначений");
ТЗ.Выгрузить(ТЗ_Дубли);
// Сворачиваем по значению, чтобы найти количество вхождений
ТЗ_Дубли.Свернуть("Значение", "Колво");
ТЗ_Дубли.ВыбратьСтроки();
Пока ТЗ_Дубли.ПолучитьСтроку() = 1 Цикл
Если ТЗ_Дубли.Колво > 1 Тогда
Сообщить("Значение " + ТЗ_Дубли.Значение + " повторено " + ТЗ_Дубли.Колво + " раз");
КонецЕсли;
КонецЦикла;
Важный нюанс: Метод со сверткой требует значительного объема оперативной памяти при очень больших справочниках, так как вся таблица значений хранится в ОЗУ.
Для продвинутых разработчиков и ситуаций, когда скорость критически важна, оптимальным решением будет использование внешних компонент, таких как 1sqlite.dll или 1C++ (класс ODBCRecordset). Прямые запросы позволяют выполнить поиск дублей на уровне ядра базы данных, минуя объектную модель 1С.
Выясним причину эффективности этого метода: драйвер базы данных обращается напрямую к файлам .dbf и использует мощные механизмы индексации. Рассмотрим пример использования компоненты 1sqlite для поиска дублей в справочнике штрихкодов:
Попытка
БазаSQL = СоздатьОбъект("SQLiteBase");
Исключение
ЗагрузитьВнешнююКомпоненту("1sqlite.dll");
БазаSQL = СоздатьОбъект("SQLiteBase");
КонецПопытки;
БазаSQL.Открыть(":memory:"); // Открываем временную базу в памяти
ЗапросSQL = БазаSQL.НовыйЗапрос();
// Текст SQL запроса с использованием группировки HAVING
ТекстЗапроса = "
|SELECT
| Спр.Code as [ШтрихКод],
| COUNT(Спр.id) as [Количество]
|FROM
| [Справочник.ШтрихКоды] as Спр
|WHERE
| Ltrim(Спр.Code) <> ''
|GROUP BY
| Спр.Code
|HAVING
| COUNT(Спр.id) > 1
|ORDER BY
| Спр.Code
|";
Результат = ЗапросSQL.ВыполнитьЗапрос(ТекстЗапроса);
Результат.ВыбратьСтроку(); // Показываем результат в таблице
Этот подход позволяет обработать 70 000 записей за доли секунды. Однако он требует наличия внешних библиотек в каталоге программы.
При поиске дублей в 1С 7.7 проанализируем следующие моменты, которые могут повлиять на результат:
Строка содержат невидимые пробелы в начале или в конце. Для 1С значения " 123" и "123" — это разные значения. При поиске дублей рекомендуется использовать функции очистки строк, если это возможно в вашем методе.Сортировка или Отбор по реквизиту, стандартные методы типа ВыбратьЭлементыПоРеквизиту() работать не будут. Для больших справочников индексация обязательна.Штрихкоды подчинены Номенклатуре), помните, что дубли могут быть допустимы в контексте разных владельцев, но недопустимы в целом по базе. Уточните задачу перед запуском обработки.Подводя итог, можно сказать: для разовой задачи на небольшой базе проще всего использовать Метод 1 (Запрос с группировкой). Если нужно быстро обработать огромный массив данных и вы знакомы с SQL — используйте Метод 3. Если же вы предпочитаете классическое манипулирование данными в памяти — Метод 2 с таблицей значений станет вашим лучшим инструментом.