При разработке конфигураций и обработок в среде 1С:Предприятие разработчики часто используют стандартный метод менеджера справочника НайтиПоРеквизиту. Однако на практике нередко возникает ситуация: в справочнике существует несколько элементов с одинаковым значением реквизита (например, дубли контрагентов с одинаковым КПП или номенклатура с одинаковым артикулом). Выясним причину, по которой платформа возвращает конкретный элемент, почему этот выбор не является детерминированным и как правильно построить алгоритм, чтобы гарантированно получать ожидаемый результат.
Проанализируем детально поведение платформы 1С, внутренние механизмы СУБД и рассмотрим готовые программные решения.
Обратимся к синтакс-помощнику платформы. В описании метода НайтиПоРеквизиту четко зафиксировано примечание: «Если существует несколько элементов с указанным значением реквизита, то будет найден только один из них». При этом платформа не декларирует никаких правил очередности.
Рассмотрим, что происходит «под капотом» при вызове данного метода:
SELECT TOP 1 ..., а для PostgreSQL — LIMIT 1. При этом платформа принципиально не добавляет предложение ORDER BY._IDRRef). В этом случае первым считывается элемент с наименьшим физическим значением ссылки в индексе.
Если реквизит не индексирован, СУБД выполняет полное сканирование таблицы (Table Scan или Clustered Index Scan), последовательно считывая страницы данных до первого совпадения.Важно понимать: Перезапись существующего элемента ничего не изменит в ключе ссылки, так как внутренний идентификатор (GUID) генерируется при создании объекта и остается неизменным на протяжении всей его жизни.
Иногда разработчики сталкиваются с тем, что один и тот же код в рабочей базе годами возвращал один элемент, а затем внезапно начал находить другой. Разберем по шагам факторы, влияющие на непредсказуемость выборки:
Если нам требуется найти ровно один элемент, но поведение должно быть строго предсказуемым, откажемся от метода НайтиПоРеквизиту в пользу прямого запроса 1С. Посмотрим на пример, где мы явно указываем критерий первенства с помощью предложения УПОРЯДОЧИТЬ ПО:
Функция НайтиКонтрагентаПоКПП(ЗначениеКПП) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.КПП = &КПП
| И НЕ Контрагенты.ПометкаУдаления
|УПОРЯДОЧИТЬ ПО
| Контрагенты.Код ВОЗР";
Запрос.УстановитьПараметр("КПП", ЗначениеКПП);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
Иначе
Возврат Справочники.Контрагенты.ПустаяСсылка();
КонецЕсли;
КонецФункции
В этом примере мы получаем однозначный детерминированный результат: всегда будет найден элемент с наименьшим кодом, не помеченный на удаление.
Если бизнес-логика предполагает обработку ситуации, когда в справочнике присутствует несколько дубликатов, разберем способ получения полного списка совпадений в массив:
Функция ПолучитьВсехКонтрагентовПоКПП(ЗначениеКПП) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.КПП = &КПП
|УПОРЯДОЧИТЬ ПО
| Контрагенты.Наименование ВОЗР";
Запрос.УстановитьПараметр("КПП", ЗначениеКПП);
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
Возврат ТаблицаРезультата.ВыгрузитьКолонку("Ссылка");
КонецФункции
После получения массива ссылок прикладной код может проанализировать каждый объект, проверить дату последнего изменения или вывести диалог пользователю для ручного разрешения коллизии.
Рассмотрим долгосрочные системные решения, позволяющие избежать ошибок неуникальности реквизитов:
ПередЗаписью реализуем проверку наличия дублей. Если элемент с таким реквизитом уже есть в базе, выдаем предупреждение и устанавливаем Отказ = Истина.Если необходимо точно исследовать, какой именно SQL-запрос отправляет платформа при выполнении метода НайтиПоРеквизиту в вашей конкретной конфигурации, применим следующие инструменты:
DBMSSQL или DBPOSTGRS с фильтрацией по контексту выполнения. В журнале будет зафиксирован итоговый текст SQL-запроса без секции сортировки.log_min_duration_statement в PostgreSQL перехватим сессию пользователя и проанализируем план выполнения (Execution Plan), где будет виден способ считывания записей со страниц диска.Главный вывод: Метод НайтиПоРеквизиту предназначен исключительно для работы с уникальными данными. Во всех остальных случаях следует использовать запросы с явной конструкцией УПОРЯДОЧИТЬ ПО.