Часто в процессе разработки или написания обработок возникает задача найти все документы в базе, в которых выбран конкретный элемент справочника (например, определенный контрагент, договор или номенклатура). Разберем по шагам несколько эффективных способов решения этой задачи, начиная от базовых встроенных функций платформы и заканчивая архитектурно правильными механизмами.
В платформе 1С существует встроенный метод НайтиПоСсылкам, который возвращает таблицу значений со всеми найденными ссылками. Рассмотрим подробнее, как правильно его использовать и каких ошибок стоит избегать.
Частая ошибка начинающих разработчиков — попытка передать в массив для поиска саму выборку или объект. В метод необходимо передавать именно ссылки. Посмотрим на пример правильного заполнения массива и вызова функции:
Выборка = Справочники.ДоговорыКонтрагентов.Выбрать();
МассивИскомыхСсылок = Новый Массив;
Пока Выборка.Следующий() Цикл
// Обязательно добавляем ссылку, а не саму выборку
МассивИскомыхСсылок.Добавить(Выборка.Ссылка);
КонецЦикла;
// Получаем таблицу значений с найденными ссылками
ТаблицаСсылок = НайтиПоСсылкам(МассивИскомыхСсылок);
Метод возвращает таблицу значений с колонками: Ссылка (где нашли), Данные (найденный объект) и Метаданные. Поскольку наша задача — найти только документы, нам необходимо отфильтровать результат, так как метод найдет ссылки и в других справочниках, регистрах и так далее.
Проанализируем ситуацию: как понять, что найденная строка принадлежит документу? Для этого есть два надежных варианта:
Метаданные.Документы.Содержит(СтрокаТЗ.Метаданные)Документы.ТипВсеСсылки().СодержитТип(ТипЗнч(СтрокаТЗ.Ссылка))Пример обхода результатов:
Для Каждого СтрокаТС Из ТаблицаСсылок Цикл
Если Метаданные.Документы.Содержит(СтрокаТС.Метаданные) Тогда
// Это документ! Получаем объект для дальнейшей работы
ТекДокОбъект = СтрокаТС.Данные.ПолучитьОбъект();
// Например, отменяем проведение
ТекДокОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
КонецЕсли;
КонецЦикла;
Важное замечание: При использовании метода НайтиПоСсылкам платформа накладывает избыточные разделяемые блокировки на читаемые таблицы СУБД. В высоконагруженных базах это может привести к зависанию работы пользователей. Поэтому для регулярных задач этот метод лучше не применять.
Если поиск нужен часто, намного эффективнее использовать запросы. Поскольку мы не знаем заранее, в каких именно документах есть нужный реквизит, мы можем собрать текст запроса динамически, перебрав метаданные документов.
Выясним причину распространенной ошибки: иногда разработчики ищут реквизит по его имени (например, "Организация" или "Контрагент"). Это неверно, так как ссылка может храниться в реквизите с именем "Грузоотправитель" или "Плательщик". Правильный подход — проверять тип реквизита: может ли он хранить искомую ссылку.
Посмотрим на пример формирования такого запроса:
ИскомаяСсылка = Справочники.ДоговорыКонтрагентов.НайтиПоКоду("00001");
ТипИскомойСсылки = ТипЗнч(ИскомаяСсылка);
ТекстЗапроса = "";
Для Каждого ВидДок Из Метаданные.Документы Цикл
// Проверяем реквизиты шапки документа
ДокументПодходит = Ложь;
Для Каждого Реквизит Из ВидДок.Реквизиты Цикл
Если Реквизит.Тип.СодержитТип(ТипИскомойСсылки) Тогда
ДокументПодходит = Истина;
Прервать; // Нашли подходящий реквизит, дальше шапку не проверяем
КонецЕсли;
КонецЦикла;
// Если документ подходит, добавляем его в общий запрос
Если ДокументПодходит Тогда
Если ТекстЗапроса <> "" Тогда
ТекстЗапроса = ТекстЗапроса + Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС;
КонецЕсли;
ТекстЗапроса = ТекстЗапроса +
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Ссылка КАК ДокументСсылка
|ИЗ
| Документ." + ВидДок.Имя + " КАК ТекДокумент
|ГДЕ
| ТекДокумент.ПометкаУдаления = ЛОЖЬ";
// В реальной задаче здесь нужно прописывать условия сравнения по конкретным найденным реквизитам
КонецЕсли;
КонецЦикла;
Если ТекстЗапроса <> "" Тогда
Запрос = Новый Запрос(ТекстЗапроса);
Результат = Запрос.Выполнить().Выгрузить();
КонецЕсли;
Самый быстрый и архитектурно правильный способ, если поиск по данному справочнику требуется в системе регулярно. В дереве конфигурации 1С есть специальная ветка Критерии отбора. Этот объект метаданных связывает между собой различные документы и справочники по заданному типу данных.
Если для нужного справочника уже настроен критерий отбора (например, "ДокументыПоДоговору"), вам достаточно обратиться к его виртуальной таблице запросом. Это работает мгновенно и не вызывает проблем с производительностью:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДокументыПоДоговору.Ссылка
|ИЗ
| КритерийОтбора.ДокументыПоДоговору(&ИскомыйДоговор) КАК ДокументыПоДоговору";
Запрос.УстановитьПараметр("ИскомыйДоговор", ИскомаяСсылка);
Выборка = Запрос.Выполнить().Выбрать();
В современных конфигурациях 1С (начиная с платформы 8.3) интегрирована БСП. Разработчикам не нужно "изобретать велосипед". В общих модулях есть готовые и протестированные методы. Например, можно использовать функции модуля ОбщегоНазначения.
Функции БСП вроде ОбщегоНазначения.НайтиСсылки() самостоятельно обрабатывают ссылки порциями, корректно работают в привилегированном режиме и исключают длительную блокировку таблиц. Если ваша база построена на БСП, всегда отдавайте предпочтение этим готовым программным интерфейсам.