Как в 1С 7.7 найти и собрать в список элементы справочника с одинаковым значением реквизита?

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

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

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

Метод 1. Использование штатного метода ВыбратьЭлементыПоРеквизиту()

Начнем с самого правильного и быстрого встроенного механизма объектной модели 1С 7.7. Платформа предоставляет нам специализированный метод ВыбратьЭлементыПоРеквизиту, который позволяет открыть выборку элементов справочника по значению конкретного реквизита. Этот метод работает крайне эффективно, так как использует готовые индексы базы данных.

Прежде чем использовать данный метод в коде, необходимо выполнить обязательное условие в Конфигураторе. У реквизита справочника, по которому будет производиться поиск, на вкладке «Дополнительно» должен быть установлен флаг «Сортировка» (в некоторых версиях он называется «Отбор»). Если этот флаг не установлен, платформа выдаст ошибку времени выполнения при попытке вызвать метод.

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


// Создаем список для хранения найденных элементов
СписокДублей = СоздатьОбъект("СписокЗначений");

// Создаем объект справочника Номенклатура
СпрНоменклатура = СоздатьОбъект("Справочник.Номенклатура");

ИскомыйАртикул = СокрЛП(ВыбАртикул);

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

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

  1. Высокая скорость работы. Поиск происходит практически мгновенно благодаря прямому обращению к индексу таблицы справочника.
  2. Абсолютная безопасность для данных. Мы не производим никаких модификаций записей и не открываем транзакции.
  3. Компактный и понятный код, который легко читать и поддерживать.

Недостатки метода:

  1. Необходимость монопольного доступа к базе данных для установки флага «Сортировка» в Конфигураторе, если он не был включен ранее. Для крупных баз данных это может стать проблемой, так как потребуется остановка работы пользователей для реструктуризации таблиц.
  2. Ограничения по типам данных. Нельзя установить сортировку для периодических реквизитов, неограниченных строк и некоторых сложных типов.

Метод 2. Опасный «транзакционный» обходной путь (НачатьТранзакцию — Найти — Удалить — ОтменитьТранзакцию)

А теперь проанализируем весьма специфический прием, который иногда называют «транзакционным поиском». Этот способ часто применяется разработчиками, когда у реквизита нет флага «Сортировка», менять конфигурацию нельзя, а использовать внешние компоненты не хочется.

Суть трюка заключается в следующем: стандартный метод НайтиПоРеквизиту умеет находить только первый элемент с подходящим значением реквизита. Чтобы заставить его найти второй, третий и последующие элементы, разработчики временно удаляют найденный элемент внутри транзакции. При следующем вызове метода платформа находит уже следующий дубликат. После завершения сбора данных транзакция принудительно отменяется методом ОтменитьТранзакцию, и все удаленные элементы возвращаются на свои места.

Посмотрим на пример реализации такого алгоритма с обработкой возможных исключений:


СписокДублей = СоздатьОбъект("СписокЗначений");
Спр = СоздатьОбъект("Справочник.Номенклатура");
ИскомыйАртикул = СокрЛП(ProductSku.Артикул);

// Запускаем транзакцию
НачатьТранзакцию();

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

// ВНИМАНИЕ! Обязательно откатываем транзакцию, чтобы вернуть элементы в базу
ОтменитьТранзакцию();

// Выводим результаты работы алгоритма
Если СписокДублей.РазмерСписка() > 0 Тогда
    Для Сч = 1 По СписокДублей.РазмерСписка() Цикл
        Эл = СписокДублей.ПолучитьЗначение(Сч);
        Сообщить("Найдено в транзакции: " + Эл.Наименование + " | Код: " + Эл.Код);
    КонецЦикла;
КонецЕсли;

ВНИМАНИЕ! Этот метод является крайне опасным «костылем»! Рассмотрим подробнее, к каким катастрофическим последствиям может привести его необдуманное использование:

  1. Риск безвозвратной потери данных. Если в момент выполнения этого цикла (особенно на базе с большим количеством элементов) произойдет сбой питания, зависание компьютера пользователя, аварийное завершение процесса или сетевой сбой, транзакция не успеет выполнить откат. Все удаленные элементы справочника будут удалены физически и исчезнут навсегда вместе со всеми подчиненными элементами и движениями! Восстановить их можно будет только из резервной копии.
  2. Запуск процедур ПриУдалении(). Вызов метода Удалить(1) инициирует выполнение предопределенной процедуры ПриУдалении(), прописанной в модуле справочника. Если там заложена тяжелая логика проверки ссылок, каскадного удаления подчиненных элементов или отправки уведомлений, это приведет к резкому падению производительности или возникновению блокировок.
  3. Жесткие блокировки базы данных. Физическое удаление записей в транзакции накладывает блокировки на таблицы СУБД (особенно критично для DBF-версий 1С 7.7). Другие пользователи не смогут записывать новые элементы или проводить документы, содержащие этот справочник, получая постоянные ошибки о блокировке таблиц.
  4. Проблема монопольного захвата объектов. Если один из дублирующихся элементов открыт на редактирование у другого пользователя в сети, метод Удалить(1) завершится ошибкой. Даже использование конструкции Попытка — Исключение не спасет ситуацию: выполнение цикла прервется, и вы получите неполный список дублей.

Метод 3. Прямые SQL-запросы через внешнюю компоненту «1С++»

Если вы работаете в SQL-версии 1С:Предприятия 7.7 или используете локальный движок для DBF через компоненту 1С++, то использование прямых SQL-запросов является стандартом «де-факто» для высоконагруженных систем. Прямой запрос обходит объектную модель платформы и обращается к таблицам базы данных на сервере напрямую.

Разберем пример прямого SQL-запроса, который находит все дублирующиеся элементы в справочнике Контрагенты по реквизиту ИНН:


ТекстЗапроса = "
|SELECT
|  Спр.ID [Элемент $Справочник.Контрагенты],
|  $Спр.ИНН ИНН
|FROM
|  $Справочник.Контрагенты Спр
|WHERE
|  $Спр.ИНН IN
|  (SELECT
|     $Спр1.ИНН
|  FROM
|     $Справочник.Контрагенты Спр1
|  WHERE
|     $Спр1.ИНН <> ''
|  GROUP BY
|     $Спр1.ИНН
|  HAVING
|     COUNT(*) > 1)
|ORDER BY
|  $Спр.ИНН";

// Создаем объект класса Query из 1С++
ЗапросSQL = СоздатьОбъект("ODBCRecordset");
ТабРезультат = ЗапросSQL.ВыполнитьИнструкцию(ТекстЗапроса);

// В ТабРезультат мы получаем готовую Таблицу Значений со списком всех дубликатов
ТабРезультат.ВыбратьСтроки();
Пока ТабРезультат.ПолучитьСтроку() = 1 Цикл
    Сообщить("Найден дубль ИНН: " + ТабРезультат.ИНН + " | Ссылка: " + ТабРезультат.Элемент);
КонецЦикла;

Преимущества прямого запроса:

  1. Абсолютный лидер по скорости работы. Обработка миллионов записей на сервере БД занимает сотые доли секунды.
  2. Никаких изменений метаданных в Конфигураторе не требуется (флаг «Сортировка» не нужен).
  3. Не накладывает деструктивных блокировок на базу данных, работает в режиме чтения.
  4. Запрос сразу возвращает готовую структуру данных (Таблицу Значений), с которой легко работать дальше в коде.

Недостатки метода:

  1. Требует наличия и корректной регистрации в системе внешней компоненты 1С++.
  2. Разработчик должен обладать базовыми знаниями синтаксиса T-SQL и механизмов метаимен 1С++.

Метод 4. Безопасная альтернатива без изменения метаданных — Свертка Таблицы Значений

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

Рассмотрим алгоритм реализации этого метода:


// 1. Создаем Таблицу Значений для сбора всех элементов справочника
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Элемент");
ТЗ.НоваяКолонка("Артикул");
ТЗ.НоваяКолонка("Количество", "Число");

Спр = СоздатьОбъект("Справочник.Номенклатура");

// Полностью обходим справочник и заполняем Таблицу Значений
Спр.ВыбратьЭлементы(0); // Выбираем без учета групп
Пока Спр.ПолучитьЭлемент() = 1 Цикл
    ТЗ.НоваяСтрока();
    ТЗ.Элемент = Спр.ТекущийЭлемент();
    ТЗ.Артикул = СокрЛП(Спр.Артикул);
    ТЗ.Количество = 1;
EndDo;

// 2. Создаем копию таблицы значений для последующего поиска соответствий
ТЗ_Копия = СоздатьОбъект("ТаблицаЗначений");
ТЗ.Выгрузить(ТЗ_Копия);

// 3. Сворачиваем исходную таблицу значений по реквизиту поиска
ТЗ.Свернуть("Артикул", "Количество");

// Теперь в ТЗ остались только уникальные Артикулы и суммарное количество их вхождений
// Нам интересны только те строки, где Количество > 1
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    Если ТЗ.Количество > 1 Тогда
        Сообщить("Найден дублирующийся артикул: " + ТЗ.Артикул + " (встречается " + ТЗ.Количество + " раз)");
        
        // Находим все элементы с этим артикулом в копии нашей таблицы
        СтрокаКопии = 0;
        Пока ТЗ_Копия.НайтиЗначение(ТЗ.Артикул, СтрокаКопии, "Артикул") = 1 Цикл
            ЭлементДубль = ТЗ_Копия.ПолучитьЗначение(СтрокаКопии, "Элемент");
            Сообщить("   -> Элемент: " + ЭлементДубль.Наименование + " | Код: " + ЭлементДубль.Код);
            
            // Чтобы не зациклиться, временно очищаем артикул в найденной строке копии ТЗ
            ТЗ_Копия.УстановитьЗначение(СтрокаКопии, "Артикул", "ОБРАБОТАНО");
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

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

  1. Абсолютная безопасность: мы работаем только в оперативной памяти компьютера с выгруженными данными, база никак не может быть повреждена.
  2. Не требуется изменять свойства реквизитов в Конфигураторе и прерывать работу пользователей.
  3. Работает в любых версиях платформы 1С 7.7 (SQL, DBF) без установки сторонних библиотек и компонент.

Недостатки метода:

  1. Потребление оперативной памяти. Если справочник содержит сотни тысяч или миллионы элементов, выгрузка всей таблицы в память может существенно замедлить работу клиентского компьютера.

Сравнительный анализ решений: что выбрать?

Проанализируем все рассмотренные нами варианты и подведем итог, выбрав оптимальное решение под конкретные условия разработки:

  1. Если у вас есть доступ к Конфигуратору и возможность установить флаг «Сортировка» у реквизита — используйте Метод 1 (ВыбратьЭлементыПоРеквизиту). Это стандартный, самый надежный и быстрый способ в рамках штатной объектной модели.
  2. Если база работает под управлением Microsoft SQL Server и в системе зарегистрирована внешняя компонента 1С++ — выбирайте Метод 3 (Прямой SQL-запрос). Он обеспечит максимальное быстродействие даже на базах объемом в десятки гигабайт.
  3. Если вам нужно одноразово найти дубли в файловой базе без изменения ее структуры — используйте Метод 4 (Свертка Таблицы Значений). Это гарантирует безопасность ваших данных и стабильность работы системы.
  4. Ни при каких обстоятельствах не используйте Метод 2 (транзакционный поиск с временным удалением) в рабочей базе. Риск безвозвратно потерять данные при случайном техническом сбое слишком велик, чтобы пренебрегать им ради экономии времени.
← На главную