Как работает метод НайтиПоРеквизиту при наличии нескольких совпадений и как получить нужный элемент в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Проанализируем детально поведение платформы 1С, внутренние механизмы СУБД и рассмотрим готовые программные решения.

Внутренняя логика работы метода НайтиПоРеквизиту на уровне СУБД

Обратимся к синтакс-помощнику платформы. В описании метода НайтиПоРеквизиту четко зафиксировано примечание: «Если существует несколько элементов с указанным значением реквизита, то будет найден только один из них». При этом платформа не декларирует никаких правил очередности.

Рассмотрим, что происходит «под капотом» при вызове данного метода:

  1. Формирование SQL-запроса платформой: Сервер 1С транслирует вызов метода в низкоуровневый SQL-запрос с ограничением количества возвращаемых строк. Для Microsoft SQL Server это конструкция SELECT TOP 1 ..., а для PostgreSQL — LIMIT 1. При этом платформа принципиально не добавляет предложение ORDER BY.
  2. Отсутствие гарантированного порядка в стандарте SQL: По правилам реляционных баз данных любая выборка без явного указания директивы упорядочивания представляет собой неупорядоченное множество. СУБД возвращает первую попавшуюся запись, удовлетворяющую условию отбора, основываясь исключительно на текущем плане выполнения (Execution Plan).
  3. Влияние индексов: Если для реквизита в конфигураторе установлено свойство «Индексировать», СУБД чаще всего выполняет поиск по некластерному индексу (Index Seek). В структуре таблиц 1С некластерный индекс содержит значение реквизита и внутренний идентификатор ссылки (_IDRRef). В этом случае первым считывается элемент с наименьшим физическим значением ссылки в индексе. Если реквизит не индексирован, СУБД выполняет полное сканирование таблицы (Table Scan или Clustered Index Scan), последовательно считывая страницы данных до первого совпадения.

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

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

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

  1. Регламентные операции СУБД: Перестроение индексов (Rebuild), дефрагментация, сжатие таблиц (Shrink) или фоновые процессы очистки (VACUUM в PostgreSQL) меняют физический порядок расположения строк на страницах базы данных.
  2. Обновление статистики: Оптимизатор СУБД может автоматически сменить план запроса с поиска по индексу на сканирование всей таблицы (или наоборот), из-за чего порядок обхода записей кардинально меняется.
  3. Кэширование данных: Если часть страниц таблицы уже загружена в буферный пул оперативной памяти СУБД, оптимизатор может отдать строку из кэша быстрее, чем прочитает другие подходящие строки с диска.
  4. Различие СУБД: На файловой базе, на MS SQL Server и на PostgreSQL результат одного и того же вызова без явной сортировки может различаться.

Способ 1. Получение первого элемента с гарантированной сортировкой через запрос

Если нам требуется найти ровно один элемент, но поведение должно быть строго предсказуемым, откажемся от метода НайтиПоРеквизиту в пользу прямого запроса 1С. Посмотрим на пример, где мы явно указываем критерий первенства с помощью предложения УПОРЯДОЧИТЬ ПО:


Функция НайтиКонтрагентаПоКПП(ЗначениеКПП) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |	Контрагенты.Ссылка КАК Ссылка
        |ИЗ
        |	Справочник.Контрагенты КАК Контрагенты
        |ГДЕ
        |	Контрагенты.КПП = &КПП
        |	И НЕ Контрагенты.ПометкаУдаления
        |УПОРЯДОЧИТЬ ПО
        |	Контрагенты.Код ВОЗР";
        
    Запрос.УстановитьПараметр("КПП", ЗначениеКПП);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Ссылка;
    Иначе
        Возврат Справочники.Контрагенты.ПустаяСсылка();
    КонецЕсли;
    
КонецФункции

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

Способ 2. Получение и анализ всех найденных элементов

Если бизнес-логика предполагает обработку ситуации, когда в справочнике присутствует несколько дубликатов, разберем способ получения полного списка совпадений в массив:


Функция ПолучитьВсехКонтрагентовПоКПП(ЗначениеКПП) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Контрагенты.Ссылка КАК Ссылка
        |ИЗ
        |	Справочник.Контрагенты КАК Контрагенты
        |ГДЕ
        |	Контрагенты.КПП = &КПП
        |УПОРЯДОЧИТЬ ПО
        |	Контрагенты.Наименование ВОЗР";
        
    Запрос.УстановитьПараметр("КПП", ЗначениеКПП);
    
    ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
    Возврат ТаблицаРезультата.ВыгрузитьКолонку("Ссылка");
    
КонецФункции

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

Способ 3. Архитектурное исключение проблемы дублирования

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

  1. Контроль уникальности при записи: В модуле объекта справочника в событии ПередЗаписью реализуем проверку наличия дублей. Если элемент с таким реквизитом уже есть в базе, выдаем предупреждение и устанавливаем Отказ = Истина.
  2. Использование регистра сведений: Если требуется хранить связь между внешним идентификатором (например, КПП, штрихкодом, артикулом) и объектом справочника, создадим регистр сведений. Назначив реквизит измерением регистра, мы гарантируем уникальность на уровне платформы или сможем отслеживать актуальный элемент во времени с помощью периодического регистра и среза последних.

Способы анализа фактического SQL-запроса

Если необходимо точно исследовать, какой именно SQL-запрос отправляет платформа при выполнении метода НайтиПоРеквизиту в вашей конкретной конфигурации, применим следующие инструменты:

  1. Настройка технологического журнала 1С (ТЖ): Настроим сбор событий DBMSSQL или DBPOSTGRS с фильтрацией по контексту выполнения. В журнале будет зафиксирован итоговый текст SQL-запроса без секции сортировки.
  2. Профайлер СУБД: С помощью SQL Server Profiler, Extended Events в MS SQL Server или параметра log_min_duration_statement в PostgreSQL перехватим сессию пользователя и проанализируем план выполнения (Execution Plan), где будет виден способ считывания записей со страниц диска.

Главный вывод: Метод НайтиПоРеквизиту предназначен исключительно для работы с уникальными данными. Во всех остальных случаях следует использовать запросы с явной конструкцией УПОРЯДОЧИТЬ ПО.

← На главную