Почему не работает ИспользоватьВладельца совместно с ВыбратьЭлементыПоРеквизиту в 1С 7.7 и как это исправить

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

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

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

В чем причина несовместимости методов? Анализируем архитектуру 1С 7.7

Для того чтобы понять природу этой проблемы, давайте заглянем во внутреннее устройство базы данных 1С 7.7 (независимо от того, используется ли DBF- или SQL-версия). Работа всех выборок в системе опирается на индексы таблиц.

Когда мы вызываем метод ИспользоватьВладельца(Владелец), мы указываем платформе, что последующая выборка должна использовать индекс по полю PARENTEXT (идентификатор владельца в таблице справочника). Система готовится читать записи последовательно, ограничиваясь рамками конкретного родительского элемента.

Однако, когда следом вызывается метод ВыбратьЭлементыПоРеквизиту(ИмяРеквизита, Значение, ...), платформа вынуждена переключить индекс. Для выполнения быстрого поиска по реквизиту (у которого в Конфигураторе установлен признак «Сортировка») 1С 7.7 задействует индекс, построенный именно по этому реквизиту. В СУБД этот индекс является глобальным для всей таблицы справочника и не связан с конкретным владельцем.

Таким образом, метод ВыбратьЭлементыПоРеквизиту() полностью сбрасывает и игнорирует ранее установленный фильтр ИспользоватьВладельца(). Платформа начинает сканировать таблицу по глобальному индексу реквизита, возвращая элементы для всех владельцев в базе данных. Простая перестановка строк местами, как предлагалось в сообщениях форума, не дает никакого результата, поскольку один метод всегда затирает контекст работы другого.

Скрытые ошибки в исходном коде автора

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

  1. Пропущенные круглые скобки у методов. В строке Пока СпрОбъекты.ПолучитьЭлемент = 1 отсутствует вызов метода как функции. Правильное написание в 1С 7.7 требует обязательного наличия скобок: ПолучитьЭлемент(). То же самое касается конструкции СправочникВидов.ТекущийЭлемент — правильно писать ТекущийЭлемент(). Отсутствие скобок может приводить к бесконечным циклам или ошибкам компиляции модуля.
  2. Неверная интерпретация результата поиска. Строка РеквизитПоиска = СправочникВидов.НайтиПоНаименованию("Банковское оборудование"); возвращает числовое значение (1 — если элемент найден, 0 — если нет). Переменная РеквизитПоиска содержит статус выполнения операции, а не ссылку на элемент справочника. И хотя далее автор использует правильный метод ТекущийЭлемент() позиционированного справочника, такое написание кода запутывает и снижает его читаемость.
  3. Контекст переменной ТекущийЭлемент(). Метод ТекущийЭлемент() без явного указания объекта возвращает ссылку на элемент того справочника, в модуле формы которого выполняется данный код. Если процедура ПриОткрытии() вызывается в обработке или отчете, этот метод вернет пустое значение, что сделает фильтр по владельцу неработоспособным изначально.

Теперь рассмотрим рабочие варианты обхода этого платформенного ограничения.

Решение 1. Выборка по владельцу с ручной фильтрацией в цикле

Этот способ является наиболее простым и предпочтительным, если у одного владельца содержится относительно небольшое количество подчиненных элементов (например, до нескольких сотен). Мы заставляем систему прочитать из базы только элементы нужного владельца, а проверку соответствия реквизиту выполняем самостоятельно внутри цикла обработки.

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


СправочникВидов = СоздатьОбъект("Справочник.БП_ВидыОбслуживаемыхОбъектов");
Если СправочникВидов.НайтиПоНаименованию("Банковское оборудование") = 1 Тогда
    ВидОбъектаДляФильтра = СправочникВидов.ТекущийЭлемент();
Иначе
    Предупреждение("Не найден вид обслуживаемых объектов!");
    Возврат;
EndЕсли;

СпрОбъекты = СоздатьОбъект("Справочник.БП_ОбъектыНаОбслуживании");
// Устанавливаем фильтр по владельцу (например, Контрагенту)
СпрОбъекты.ИспользоватьВладельца(ТекущийЭлемент()); 

// Открываем стандартную последовательную выборку по владельцу
СпрОбъекты.ВыбратьЭлементы();

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

Преимущество метода: Высокая скорость работы при небольшом количестве элементов у одного владельца, так как выборка ограничена рамками одного родителя напрямую на уровне СУБД.

Решение 2. Выборка по реквизиту с ручной фильтрацией владельца

Этот вариант эффективен в противоположной ситуации: когда элементов с определенным реквизитом (например, «Банковское оборудование») во всей базе данных очень мало, но у конкретного владельца подчиненных объектов может быть огромное количество. Мы выбираем элементы по реквизиту глобально по всей базе данных, а в цикле отсекаем те, которые принадлежат "чужим" владельцам.

Проанализируем пример реализации такого подхода:


СправочникВидов = СоздатьОбъект("Справочник.БП_ВидыОбслуживаемыхОбъектов");
Если СправочникВидов.НайтиПоНаименованию("Банковское оборудование") = 1 Тогда
    ВидОбъектаДляФильтра = СправочникВидов.ТекущийЭлемент();
Иначе
    Возврат;
КонецЕсли;

СпрОбъекты = СоздатьОбъект("Справочник.БП_ОбъектыНаОбслуживании");
// Выбираем абсолютно все элементы базы с нужным реквизитом
СпрОбъекты.ВыбратьЭлементыПоРеквизиту("ВидОбъекта", ВидОбъектаДляФильтра, 0, 0);

ВладелецФильтра = ТекущийЭлемент(); // Получаем ссылку на текущего владельца

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

Преимущество метода: Минимизирует количество итераций цикла, если искомый вид объекта встречается крайне редко в масштабах всей информационной базы.

Решение 3. Использование встроенного механизма запросов (Оптимальный для больших баз)

Если информационная база имеет внушительные объемы данных (десятки и сотни тысяч записей), то перебор элементов в цикле штатными средствами справочника может вызвать заметные задержки интерфейса. В таких случаях лучшим решением будет использование объекта Запрос. Запрос позволяет выполнить фильтрацию по обоим условиям одновременно на уровне СУБД, возвращая в код только полностью готовый и отфильтрованный результат.

Рассмотрим структуру такого запроса:


СправочникВидов = СоздатьОбъект("Справочник.БП_ВидыОбслуживаемыхОбъектов");
Если СправочникВидов.НайтиПоНаименованию("Банковское оборудование") = 0 Тогда
    Возврат;
КонецЕсли;

НужныйВид = СправочникВидов.ТекущийЭлемент();
НужныйВладелец = ТекущийЭлемент();

Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = 
"//{{ЗАПРОС(ВыбратьОбъекты)
|ТекущийВладелец = Справочник.БП_ОбъектыНаОбслуживании.Владелец;
|ВидОбъекта      = Справочник.БП_ОбъектыНаОбслуживании.ВидОбъекта;
|Объект          = Справочник.БП_ОбъектыНаОбслуживании.ТекущийЭлемент;
|СерийныйНомер   = Справочник.БП_ОбъектыНаОбслуживании.СерийныйНомер;
|Сотрудник       = Справочник.БП_ОбъектыНаОбслуживании.Сотрудник;
|Подразделение   = Справочник.БП_ОбъектыНаОбслуживании.Подразделение;
|Группировка Объект без групп;
|Условие(ТекущийВладелец = НужныйВладелец);
|Условие(ВидОбъекта = НужныйВид);
|"//}}ЗАПРОС
;

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

Преимущество метода: Максимальное быстродействие на SQL-версиях 1С 7.7. СУБД самостоятельно строит оптимальный план выполнения и возвращает только релевантные строки, избавляя сервер приложений от лишнего сетевого трафика и ручного перебора в цикле.

Резюме

Совместить методы ИспользоватьВладельца() и ВыбратьЭлементыПоРеквизиту() в одной выборке встроенного языка 1С 7.7 невозможно из-за ограничений индексной системы платформы. Для решения этой задачи всегда используйте один из рассмотренных нами обходных путей в зависимости от структуры ваших данных и требований к производительности системы.

← На главную