Как установить отбор в динамическом списке на управляемой форме, отображаемом в виде дерева или иерархического списка?

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

Мы сталкиваемся с распространенной задачей в разработке на платформе 1С:Предприятие 8.3: необходимо установить отбор в динамическом списке, который отображается в виде дерева или иерархического списка на управляемой форме. Основная сложность заключается в том, что стандартные механизмы отбора, как правило, не действуют на группы в этих режимах отображения, что приводит к появлению пустых групп или неполному отбору. Давайте рассмотрим несколько подходов к решению этой проблемы.

Решение 1: Отбор с помощью произвольного запроса

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


ВЫБРАТЬ
    СправочникКаталог.Код КАК Код,
    СправочникКаталог.Наименование КАК Наименование
ИЗ
    Справочник.Каталог КАК СправочникКаталог
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (
    ВЫБРАТЬ
        Каталог.Ссылка КАК Ссылка
    ИЗ
        Справочник.Каталог КАК Каталог
    ГДЕ
        Каталог.Ссылка В(&Ссылки) // Используем параметр &Ссылки
) КАК Фильтр
ПО СправочникКаталог.Ссылка = Фильтр.Ссылка

В этом запросе мы используем параметр &Ссылки, в который будем передавать массив ссылок на те элементы справочника "Каталог", которые мы хотим отобразить. Обратите внимание, что данный запрос выбирает только те элементы, ссылки которых находятся в параметре &Ссылки. Он **не будет автоматически включать родительские группы** для найденных элементов. Если ваша задача — просто показать отфильтрованные элементы без их иерархической привязки, то этот подход будет работать. Шаг 2: Программное формирование массива ссылок и установка параметра Теперь нам нужно сформировать массив &Ссылки, основываясь на нашем условии отбора (например, по реквизиту "Модель") и передать его в динамический список. Это делается программно на сервере. Предположим, у нас есть процедура на сервере, которая вызывается при выборе модели:


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

    Ссылки = Новый Массив; // Создаем массив для хранения отобранных ссылок
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий Цикл
        Ссылки.Добавить(Выборка.Ссылка); // Добавляем найденную ссылку в массив
    КонецЦикла;

    // Устанавливаем параметр &Ссылки для основного запроса динамического списка
    // "Каталог" - это имя элемента формы типа "ДинамическийСписок"
    Элементы.Каталог.Параметры.УстановитьЗначениеПараметра("Ссылки", Ссылки);
    Элементы.Каталог.Обновить(); // Обновляем данные динамического списка
КонецПроцедуры

Этот код выполняет следующие действия: 1. Создает временный запрос, который находит все элементы справочника "Каталог", у которых реквизит Модель равен переданному значению. 2. Результаты этого запроса (ссылки на элементы) собираются в массив Ссылки. 3. Массив Ссылки передается как параметр &Ссылки в основной запрос динамического списка. 4. Динамический список обновляется, отображая только те элементы, чьи ссылки были переданы. Шаг 3: Включение родительских групп в выборку (для полной иерархии) Если нам нужно, чтобы динамический список показывал не только найденные элементы, но и всю иерархию их родителей до верхнего уровня, то задача значительно усложняется. Простое ВНУТРЕННЕЕ СОЕДИНЕНИЕ не приведет к автоматическому включению родителей. Мы должны явно добавить ссылки на всех вышестоящих родителей в наш массив Ссылки. Один из предложенных в обсуждении подходов (сообщение 2), особенно актуальный для больших объемов данных (как 350 000 групп, упомянутых в сообщении 7), включает следующие этапы: 1. Первичный отбор: Сначала отбираем все элементы, которые удовлетворяют заданному условию (например, Каталог.Модель = &Модель). 2. Поиск родителей: Для каждого найденного элемента необходимо найти всех его вышестоящих родителей. Это можно сделать несколькими способами: * Итеративный обход: Для каждого найденного элемента последовательно получать его родителя, затем родителя родителя и так до корневого элемента, добавляя все найденные родительские ссылки в наш массив Ссылки. Это может быть очень затратно по времени и ресурсам при большом количестве элементов и глубокой иерархии. * Рекурсивные CTE (Common Table Expressions): В некоторых СУБД (например, MS SQL Server) можно использовать рекурсивные запросы (CTE) для построения полной иерархии вверх от набора дочерних элементов. Однако в 1С прямые рекурсивные CTE не всегда просто реализовать в рамках конструктора запросов, и часто приходится прибегать к временным таблицам или программному обходу. 3. Кэширование родительских связей: Для значительной оптимизации поиска вышестоящих родителей при больших объемах данных предлагается использовать вспомогательный регистр сведений. Например, можно создать регистр с измерениями "Родитель" и "ВышестоящийРодитель" и заполнять/обновлять его при записи группы номенклатуры. Это позволит получать всех вышестоящих родителей для данного элемента или группы одним запросом к этому регистру, вместо многократных обращений к справочнику. При этом регистр будет относительно небольшим (содержащим только группы), и основная нагрузка ляжет на событие "ПриЗаписи" элементов-групп. Таким образом, для отображения полной иерархии до отобранных элементов, нам необходимо расширить массив Ссылки не только самими элементами, но и всеми их родителями. Особенности и потенциальные проблемы производительности: Несмотря на то, что подход с произвольным запросом и передачей массива ссылок (включая родителей) обеспечивает необходимый отбор, он может привести к заметному замедлению работы. Это связано с несколькими факторами: * Объем передаваемых данных: Если количество отобранных элементов и их родителей велико, массив Ссылки может стать очень большим, что увеличивает объем данных, передаваемых между клиентом и сервером. * Особенности работы динамического списка: В режимах "Дерево" или "Иерархический список" 1С может считывать данные порциями или выполнять отдельные запросы при раскрытии узлов дерева. Если основной запрос динамического списка имеет сложную логику, это может привести к множеству "тяжелых" запросов к серверу. * Проблема "основной таблицы": Как было упомянуто в обсуждении (сообщение 10), если динамический список имеет "основную таблицу" (т.е. построен на базе одного объекта метаданных без произвольного запроса), он в любом случае может считывать все дерево, что нивелирует усилия по фильтрации. Убедитесь, что ваш динамический список использует произвольный запрос, а не просто "основную таблицу", если вы хотите полностью контролировать выборку. Необходимо тщательно тестировать производительность решения на реальных объемах данных.

Решение 2: Использование условного оформления для скрытия групп

Если требование не состоит в полном исключении групп из выборки на уровне запроса, а скорее в визуальном скрытии пустых групп (т.е. групп, которые не содержат элементов, удовлетворяющих отбору, или не соответствуют другим критериям), мы можем воспользоваться механизмом условного оформления динамического списка. Для этой задачи есть универсальная обработка скрытия элементов в динамических списках. Принцип работы: Мы создаем правило условного оформления, которое будет проверять свойства текущего элемента динамического списка. Если элемент является группой и при этом не содержит подходящих дочерних элементов или не удовлетворяет заданному условию, мы устанавливаем для него свойство Видимость в Ложь. Пример кода: Этот код добавляется, например, в обработчик ПриСозданииНаСервере формы. Предположим, что Список — это ваш динамический список на форме, а ГруппаКонструктора — ссылка на группу, по которой мы хотим осуществлять отбор (или массив таких групп).


// Инициализируем группу, по которой будем отбирать (пример)
// В реальной ситуации ГруппаКонструктора будет получена программно или из настроек
// ГруппаКонструктора = Справочники.Номенклатура.НайтиПоКоду("00-00000001"); // Пример ссылки на группу

// Создаем элемент условного оформления для скрытия нежелательных элементов/групп
ЭлементОформленияДляСкрытия = Список.УсловноеОформление.Элементы.Добавить();
ЭлементОформленияДляСкрытия.Оформление.УстановитьЗначениеПараметра("Видимость", Ложь); // Устанавливаем невидимость

// Задаем условие, при котором элемент будет скрыт
// Например, скрываем все, что НЕ находится в иерархии ГруппаКонструктора.
// Это может потребовать предварительного формирования ГруппаКонструктора как ссылки на группу
// или Массива ссылок, если хотим показывать несколько иерархий.
ОтборПоГруппеДляСкрытия = ЭлементОформленияДляСкрытия.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ОтборПоГруппеДляСкрытия.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
ОтборПоГруппеДляСкрытия.ВидСравнения = ВидСравненияКомпоновкиДанных.НЕВИерархии; // Условие "НЕ В Иерархии"
ОтборПоГруппеДляСкрытия.ПравоеЗначение = ГруппаКонструктора; // Ссылка на базовую группу/элемент или Массив ссылок

// Также можно добавить более сложные условия, например, чтобы скрывать группы,
// которые являются пустыми после применения других отборов.
// Для этого часто требуется использовать вычисляемые поля или дополнительные запросы,
// чтобы определить "пустоту" группы.

Важные моменты при использовании условного оформления: * Производительность: Механизм условного оформления работает на уже полученных данных. Это означает, что все данные, включая те элементы и группы, которые впоследствии будут скрыты, сначала считываются с сервера, а затем уже на клиентской стороне происходит их фильтрация по видимости. На больших объемах данных такой подход может привести к значительным торможениям и неэффективному использованию ресурсов, поскольку вся информация сначала передается, а затем только обрабатывается. * Применимость: Этот метод более подходит, когда вам нужно просто визуально скрыть часть дерева, но не является критичным уменьшение объема данных, передаваемых с сервера. Это может быть приемлемо для небольших и средних списков, где производительность клиента не является узким местом. * Сложность условий: Для определения того, является ли группа "пустой" или не подходящей для отображения, могут потребоваться сложные условия в отборе условного оформления или даже дополнительные вычисляемые поля в запросе динамического списка.

Дополнительные методы и рекомендации по оптимизации

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

  1. Произвольный запрос: Предоставляет полный контроль над выборкой данных (поможет настройка поведения списков и форм выбора без программирования), позволяя включать в запрос сложные условия отбора, в том числе иерархические. Однако при этом теряется часть автоматической функциональности, связанной с основной таблицей, и может потребоваться более тщательная оптимизация.
  2. Вложенные запросы и временные таблицы: Для построения сложной иерархии с учетом отбора можно сначала сформировать список всех элементов, удовлетворяющих отбору, затем "поднять" по иерархии всех их родителей, и уже эти данные использовать в основном запросе динамического списка. Это позволяет получить полную иерархию, содержащую только нужные элементы и их предков.
  3. Передача параметров запроса: Отбор можно передавать в запрос динамического списка через параметры. В обработчике ПриСозданииНаСервере() формы можно установить значения этих параметров.
  4. Индексы: При соединении таблиц в запросах динамического списка всегда используйте индексы. Правильно настроенные индексы значительно ускоряют выполнение запросов, особенно при сложных условиях отбора и соединений.
  5. Избегайте сложных конструкций в запросах: По возможности избегайте использования вложенных запросов, большого количества соединений таблиц, условий ИЛИ, НЕ, функций работы со строками (например, ПОДСТРОКА), функций работы с датами (например, МЕСЯЦ), конструкций ВЫБОР КОГДА, а также динамических (вычисляемых) полей. Эти конструкции часто препятствуют использованию индексов и могут значительно замедлить выполнение запроса.
  6. Свойство "Динамическое считывание данных": Это свойство динамического списка влияет на то, какими порциями данные считываются из базы данных. Если запрос выполняется быстро и в выборке обычно много строк, возможно, лучше не устанавливать этот флаг. Если запрос "тяжелый", а записей мало или прокрутка списка используется редко, установка флага может быть полезной, так как это снизит начальную нагрузку.
  7. Пользовательские настройки: Рекомендуем убирать ненужные колонки из динамического списка, а также избегать вывода дополнительных полей/характеристик через точку (например, Номенклатура.ВидНоменклатуры.Наименование), так как это может сильно снизить производительность из-за дополнительных соединений таблиц.
  8. Регламентные операции СУБД: Регулярное обновление статистик и обслуживание индексов на уровне СУБД (например, MS SQL Server) также влияют на общую производительность системы и скорость выполнения запросов.
  9. Ограничение полей отбора: Начиная с версии платформы 8.3.10 появился метод УстановитьОграниченияИспользованияВОтборе(), который позволяет запрещать пользователям устанавливать отборы по конкретным полям и их реквизитам. Это может быть полезно для предотвращения создания пользователями "тяжелых" отборов, которые могут привести к замедлению работы списков со сложными запросами.

Альтернативные подходы

В некоторых особо сложных случаях, когда стандартные механизмы динамического списка не позволяют реализовать требуемую логику отбора и отображения иерархии (например, если при скрытии родителя его дочерние элементы должны стать дочерними для "деда", или при необходимости совершенно нестандартного построения иерархии), иногда приходится прибегать к полностью программному построению дерева значений и его последующему отображению на форме. Этот метод дает максимальный контроль над структурой и содержанием дерева, но требует значительно большего объема кода и может быть менее производительным для очень больших объемов данных из-за необходимости полной загрузки всех данных в оперативную память для построения дерева. Однако для специфических требований к иерархии это может быть единственным жизнеспособным решением.

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

← На главную