Как найти дубликаты кодов в справочнике 1С 7.7 с ограничением по длине кодов

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

Почему штатный «черный» запрос 1С 7.7 не подходит?

Разработчики, привыкшие к возможностям платформы 1С v8.х, часто пытаются решить эту задачу стандартным встроенным запросом. Однако встроенный язык запросов 1С 7.7 (так называемый «черный запрос») имеет жесткие ограничения. В нем нельзя использовать строковые функции, такие как Лев(), СокрЛП() или Формат(), непосредственно внутри условий группировки или выборки. Попытка написать конструкцию вроде Группировка Лев(Код, 8) приведет к синтаксической ошибке на этапе компиляции модуля.

Поэтому нам приходится использовать альтернативные пути: работу с коллекциями в оперативной памяти, прямые запросы к СУБД (для SQL-версий) или виртуальные базы данных в ОЗУ.

Способ 1. Оптимальный штатный метод через Таблицу Значений (без внешних компонент)

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

Этот способ работает в десятки раз быстрее обычного перебора методом НайтиЗначение(), так как сортировка больших массивов данных в оперативной памяти 1С 7.7 оптимизирована на уровне платформы.

Разберем по шагам алгоритм реализации:

  1. Создаем объект ТаблицаЗначений и добавляем в него две колонки: для хранения ссылки на элемент справочника и для усеченного до 8 символов кода.
  2. Обходим справочник линейным выбором элементов, заполняя таблицу. При записи кода обрезаем его функцией Лев() и предварительно очищаем от пробелов функцией СокрЛП().
  3. Сортируем полученную таблицу по колонке с усеченным кодом.
  4. Проходим циклом по отсортированной таблице, начиная со второй строки, и сравниваем код текущей строки с кодом предыдущей строки. При совпадении фиксируем дубликат.

Посмотрим на практический пример кода:


Процедура НайтиДубликатыШтатнымиСредствами()
    // Создаем таблицу значений для анализа данных в ОЗУ
    ТЗ = СоздатьОбъект("ТаблицаЗначений");
    ТЗ.НоваяКолонка("Элемент");
    ТЗ.НоваяКолонка("Код8");

    // Выбираем элементы справочника
    Спр = СоздатьОбъект("Справочник.Номенклатура");
    Спр.ВыбратьЭлементы();
    
    Пока Спр.ПолучитьЭлемент() = 1 Цикл
        // Исключаем группы, если дубликаты ищем только среди элементов
        Если Спр.ЭтоГруппа() = 1 Тогда
            Продолжить;
        КонецЕсли;
        
        ТЗ.НоваяСтрока();
        ТЗ.Элемент = Спр.ТекущийЭлемент();
        // Обрезаем код до 8 символов, предварительно убрав пробелы
        ТЗ.Код8 = Лев(СокрЛП(Спр.Код), 8);
    КонецЦикла;

    // Быстро сортируем таблицу по усеченному коду
    ТЗ.Сортировать("Код8");

    // Ищем дубликаты путем последовательного сравнения соседних строк
    КоличествоСтрок = ТЗ.КоличествоСтрок();
    Для НомерСтроки = 2 По КоличествоСтрок Цикл
        ТекущийКод = ТЗ.ПолучитьЗначение(НомерСтроки, "Код8");
        ПредыдущийКод = ТЗ.ПолучитьЗначение(НомерСтроки - 1, "Код8");
        
        Если ТекущийКод = ПредыдущийКод Тогда
            Сообщить("Обнаружен дубликат кода: " + ТекущийКод);
            Сообщить("  - Элемент 1: " + ТЗ.ПолучитьЗначение(НомерСтроки - 1, "Элемент"));
            Сообщить("  - Элемент 2: " + ТЗ.ПолучитьЗначение(НомерСтроки, "Элемент"));
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Способ 2. Быстрый прямой SQL-запрос (для SQL-версий 1С 7.7)

Если вы работаете на SQL-версии платформы 1С 7.7, самым производительным решением будет выполнение прямого запроса к базе данных Microsoft SQL Server. Это позволит выполнить всю тяжелую работу по группировке и поиску совпадений непосредственно на стороне СУБД, вернув в 1С готовый результат за доли секунды.

Простые конструкции запросов с использованием подзапросов через условие WHERE LEFT(code, 8) IN (SELECT...) могут сильно нагружать сервер БД и приводить к избыточному сканированию таблиц (Table Scan). Чтобы избежать падения производительности, мы применим оптимизированное внутреннее соединение INNER JOIN с предварительно сгруппированным подзапросом.

Обратите внимание, что для поиска именно дубликатов (совпадений от двух и более раз) в секции фильтрации агрегатных функций HAVING мы должны использовать условие строго больше единицы: COUNT(*) > 1.

Пример SQL-запроса для поиска дубликатов:


SELECT 
    main_table.id as [Элемент $Справочник.Номенклатура],
    main_table.code as [ПолныйКод],
    sub_table.code8 as [УрезанныйКод]
FROM 
    $Справочник.Номенклатура as main_table
INNER JOIN (
    -- Шаг 1: Группируем элементы по первым 8 символам кода и ищем повторения
    SELECT 
        LEFT(code, 8) as code8
    FROM 
        $Справочник.Номенклатура
    GROUP BY 
        LEFT(code, 8)
    HAVING 
        COUNT(*) > 1
) as sub_table ON LEFT(main_table.code, 8) = sub_table.code8
ORDER BY 
    sub_table.code8

Использование этого запроса через внешнюю компоненту 1С++ позволяет моментально получить объект типа ИндексированнаяТаблица с готовым списком дублирующихся позиций номенклатуры даже на базах объемом в миллионы строк.

Способ 3. Использование SQLite и 1С++ (для файловых баз данных DBF)

Что делать, если база данных файловая (DBF), но объектов в ней слишком много для комфортной обработки в оперативной памяти средствами встроенного языка 1С? В этом случае отличным компромиссом является использование встроенного SQL-движка SQLite, предоставляемого компонентой 1С++.

Мы можем выгрузить данные во временную базу данных SQLite прямо в оперативной памяти (режим :memory:) и выполнить SQL-запрос к ней локально на клиентском компьютере.

При составлении запроса для SQLite важно помнить о синтаксических нюансах:

Проанализируем пример программной реализации этого метода:


Процедура НайтиДубликатыЧерезSQLite()
    ТекстЗапроса = "
    |SELECT
    |    id [Номенклатура $Справочник.Номенклатура],
    |    code as ПолныйКод,
    |    substr(code, 1, 8) as Код8
    |FROM 
    |    Справочник_Номенклатура
    |WHERE 
    |    substr(code, 1, 8) IN (
    |        SELECT substr(code, 1, 8) 
    |        FROM Справочник_Номенклатура 
    |        GROUP BY substr(code, 1, 8) 
    |        HAVING count(*) > 1
    |    )
    |ORDER BY 
    |    Код8
    |";

    // Инициализируем локальную базу данных SQLite в ОЗУ
    База = СоздатьОбъект("SQLiteBase");
    База.Открыть(":memory:");
    
    Запрос = База.НовыйЗапрос();
    итДанные = СоздатьОбъект("ИндексированнаяТаблица");
    
    // Выполняем локальный SQL-запрос
    Запрос.ВыполнитьЗапрос(ТекстЗапроса, итДанные);
    
    // Выводим результаты в отчет
    итДанные.ВыбратьСтроки();
    Пока итДанные.ПолучитьСтроку() = 1 Цикл
        Сообщить("Урезанный код: " + итДанные.Код8 + 
                 " | Элемент: " + итДанные.Номенклатура + 
                 " | Полный код базы: " + итДанные.ПолныйКод);
    КонецЦикла;
КонецПроцедуры

Подведем итоги

Мы детально рассмотрели три инструмента поиска дубликатов кодов в 1С 7.7. Для быстрого разового анализа небольших справочников идеально подойдет Способ 1 (Таблица Значений) — он не требует установки внешних компонент и прост в отладке. Для крупных промышленных баз данных на MS SQL Server безальтернативным лидером по скорости работы является Способ 2 (прямой SQL-запрос). Если же перед вами стоит задача оптимизировать работу на файловой базе данных, связка из 1С++ и SQLite (Способ 3) обеспечит максимальную производительность без необходимости перехода на клиент-серверную архитектуру.

← На главную