При разработке и поддержке конфигураций на платформе 1С:Предприятие 7.7 программисты часто сталкиваются с неочевидным поведением стандартных методов работы со справочниками. Одна из классических проблем возникает при попытке выбрать элементы подчиненного справочника с одновременной фильтрацией по владельцу и значению определенного реквизита. Разработчик пишет интуитивно понятный код, сочетая методы ИспользоватьВладельца() и ВыбратьЭлементыПоРеквизиту(), однако платформа неожиданно игнорирует отбор по владельцу и возвращает элементы со всей базы данных.
В этой статье мы подробно разберем, почему возникает эта проблема, изучим внутренние механизмы работы платформы 1С 7.7 и рассмотрим два надежных практических способа обхода этого ограничения.
Давайте проанализируем исходную ситуацию. У нас есть подчиненный справочник (например, БП_ОбъектыНаОбслуживании), элементы которого привязаны к конкретному владельцу. Перед нами стоит задача: выбрать только те элементы этого справочника, которые принадлежат текущему владельцу и одновременно имеют определенное значение реквизита (например, ВидОбъекта).
Типичный код, который приводит к ошибке, выглядит следующим образом:
СпрОбъекты = СоздатьОбъект("Справочник.БП_ОбъектыНаОбслуживании");
СпрОбъекты.ИспользоватьВладельца(ТекущийЭлемент);
// Пытаемся выбрать элементы по реквизиту в рамках установленного владельца
СпрОбъекты.ВыбратьЭлементыПоРеквизиту("ВидОбъекта", СправочникВидов.ТекущийЭлемент, 0, 0);
При выполнении этого фрагмента кода метод ИспользоватьВладельца() полностью игнорируется. Происходит это из-за архитектурных особенностей СУБД-движка платформы 1С 7.7 (особенно ярко это проявляется в SQL-версиях баз данных).
Когда мы вызываем метод ВыбратьЭлементыПоРеквизиту(), платформа строит прямой SQL-запрос или использует соответствующий индекс DBF-таблицы по выбранному реквизиту (для которого в Конфигураторе установлен признак «Сортировка»). При этом наложение фильтра по полю внешнего владельца (поле PARENTEXT в терминах СУБД 1С 7.7) попросту опускается. Платформа «считает», что раз разработчик ищет по конкретному реквизиту, то этот индекс является приоритетным, и отбор по владельцу сбрасывается. В результате в выборку попадают все элементы подчиненного справочника, у которых совпадает реквизит, независимо от того, кто является их владельцем.
Для решения этой проблемы мы можем использовать два проверенных временем подхода.
Если подчиненный справочник содержит небольшое или среднее количество элементов для каждого владельца, наиболее простым и надежным решением будет использование метода ВыбратьЭлементы() в связке с ручной проверкой реквизита внутри цикла обхода.
Метод ВыбратьЭлементы() работает с ранее установленным фильтром ИспользоватьВладельца() абсолютно корректно и стабильно во всех версиях платформы 1С 7.7.
Давайте пошагово рассмотрим пример реализации этого метода:
// Шаг 1. Находим нужный вид обслуживания для фильтрации
СправочникВидов = СоздатьОбъект("Справочник.БП_ВидыОбслуживаемыхОбъектов");
Если СправочникВидов.НайтиПоНаименованию("Банковское оборудование") = 1 Тогда
НужныйВидОбъекта = СправочникВидов.ТекущийЭлемент();
Иначе
Предупреждение("Не найден вид обслуживаемого оборудования!");
Возврат;
КонецЕсли;
// Шаг 2. Инициализируем подчиненный справочник
СпрОбъекты = СоздатьОбъект("Справочник.БП_ОбъектыНаОбслуживании");
// Шаг 3. Устанавливаем жесткий фильтр по владельцу
СпрОбъекты.ИспользоватьВладельца(ТекущийЭлемент);
// Шаг 4. Открываем стандартную выборку (без указания реквизита)
СпрОбъекты.ВыбратьЭлементы();
// Шаг 5. Обходим только элементы текущего владельца в цикле
Пока СпрОбъекты.ПолучитьЭлемент() = 1 Цикл
// Ручной отбор: если реквизит не совпадает с искомым, пропускаем итерацию
Если СпрОбъекты.ВидОбъекта <> НужныйВидОбъекта Тогда
Продолжить;
КонецЕсли;
// Дальнейшая логика обработки элемента, прошедшего оба фильтра
Если СпрОбъекты.НаОбслуживании.Получить(ИспользоватьДату) = 1 Тогда
СпрОбъектыБанк.НоваяСтрока();
СпрОбъектыБанк.Объект = СпрОбъекты.ТекущийЭлемент;
СпрОбъектыБанк.СерийныйНомер = СпрОбъекты.СерийныйНомер.ЗаводскойНомер;
СпрОбъектыБанк.Выбран = "-";
СпрОбъектыБанк.Механик = СпрОбъекты.Сотрудник;
СпрОбъектыБанк.ДатаРемонта = СпрОбъекты.ДатаРемонта.Получить(ТекущаяДата);
СпрОбъектыБанк.Подразделение = СпрОбъекты.Подразделение;
ОбслуживаемоеКоличество = ОбслуживаемоеКоличество + 1;
КонецЕсли;
КонецЦикла;
Этот способ гарантирует стабильность результата. Платформа сначала быстро отсекает элементы по индексу владельца, а затем в оперативной памяти проверяет соответствие реквизита ВидОбъекта.
Если ваш подчиненный справочник содержит десятки или сотни тысяч элементов, то последовательный перебор всех элементов конкретного владельца в цикле на клиенте может вызвать задержки производительности. В таких ситуациях оптимально использовать объект Запрос. СУБД выполнит фильтрацию по обоим полям (и по владельцу, и по реквизиту) на уровне сервера и вернет в 1С готовый оптимизированный результат.
Разберем, как правильно составить и выполнить такой запрос:
// Шаг 1. Подготавливаем условия для запроса
СправочникВидов = СоздатьОбъект("Справочник.БП_ВидыОбслуживаемыхОбъектов");
Если СправочникВидов.НайтиПоНаименованию("Банковское оборудование") = 1 Тогда
ПараметрВидОбъекта = СправочникВидов.ТекущийЭлемент();
Иначе
Возврат;
КонецЕсли;
ПараметрВладелец = ТекущийЭлемент; // Передаем ссылку на владельца из контекста формы
// Шаг 2. Создаем и инициализируем объект Запрос
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(ВыбратьПоВладельцуИРеквизиту)
|ЭлементСпр = Справочник.БП_ОбъектыНаОбслуживании.ТекущийЭлемент;
|Владелец = Справочник.БП_ОбъектыНаОбслуживании.Владелец;
|ВидОбъекта = Справочник.БП_ОбъектыНаОбслуживании.ВидОбъекта;
|СерийныйНомер = Справочник.БП_ОбъектыНаОбслуживании.СерийныйНомер.ЗаводскойНомер;
|Сотрудник = Справочник.БП_ОбъектыНаОбслуживании.Сотрудник;
|ДатаРемонта = Справочник.БП_ОбъектыНаОбслуживании.ДатаРемонта;
|Подразделение = Справочник.БП_ОбъектыНаОбслуживании.Подразделение;
|Группировка ЭлементСпр без групп;
|Условие(Владелец = ПараметрВладелец);
|Условие(ВидОбъекта = ПараметрВидОбъекта);
|";
// Шаг 3. Выполняем запрос и обрабатываем результат
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
// Быстрое заполнение таблицы значений результатами запроса
СпрОбъектыБанк.НоваяСтрока();
СпрОбъектыБанк.Объект = Запрос.ЭлементСпр;
СпрОбъектыБанк.СерийныйНомер = Запрос.СерийныйНомер;
СпрОбъектыБанк.Выбран = "-";
СпрОбъектыБанк.Механик = Запрос.Сотрудник;
СпрОбъектыБанк.ДатаРемонта = Запрос.ДатаРемонта;
СпрОбъектыБанк.Подразделение = Запрос.Подразделение;
ОбслуживаемоеКоличество = ОбслуживаемоеКоличество + 1;
КонецЦикла;
КонецЕсли;
Использование запроса исключает лишний трафик между сервером и клиентом и моментально решает проблему некорректно работающего индекса при отборе.
Проанализируем еще одну скрытую и очень распространенную ошибку, из-за которой метод ИспользоватьВладельца() может не работать. Она связана с состоянием самого элемента-владельца на момент выполнения кода.
Если данный алгоритм отрабатывает при открытии формы нового, еще не записанного в базу данных элемента, то его ТекущийЭлемент равен пустому значению. У нового объекта еще нет уникального идентификатора (ссылки) в базе данных.
Передавая незаписанный ТекущийЭлемент в качестве параметра владельца, мы заставляем систему искать подчиненные объекты для пустой ссылки. В результате выборка всегда будет возвращать пустое значение.
Чтобы избежать этой проблемы, обязательно проверяйте, записан ли элемент-владелец в базу данных перед выполнением выборки подчиненных элементов:
Если Выбран() = 0 Тогда
// Объект еще новый и не сохранен в БД
Предупреждение("Для отображения подчиненных объектов сначала запишите текущий элемент!");
Возврат;
КонецЕсли;
// Если проверка пройдена, выполняем логику выборки
СпрОбъекты.ИспользоватьВладельца(ТекущийЭлемент);
// ... дальнейший код ...
Для стабильной и быстрой работы конфигураций 1С 7.7 придерживайтесь следующих правил:
ИспользоватьВладельца() и ВыбратьЭлементыПоРеквизиту() для одного объекта справочника.ИспользоватьВладельца() + ВыбратьЭлементы() с фильтрацией неподходящих элементов через Продолжить внутри цикла Пока.Запрос, накладывая условия на поля Владелец и нужный реквизит.Выбран() перед передачей его в качестве владельца выборки.