Как настроить отбор по табличной части в динамическом списке через расширение в 1С 8.3?

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

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

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

Главная опасность: почему нельзя использовать простое левое соединение?

Первое побуждение любого разработчика при решении такой задачи — заимствовать форму списка в расширение, открыть конструктор запроса динамического списка и добавить ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) справочника с его новой табличной частью по полю Ссылка. Однако делать этого категорически нельзя.

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

Более того, если у вашего динамического списка заполнено свойство Основная таблица (а это необходимо для стандартного функционирования списка: добавления, удаления, изменения элементов), платформа 1С при формировании списка выдаст критическую ошибку:

«Обнаружено дублирование ключевых значений в колонках: "Ссылка". Отображение данных в списке невозможно».

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

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

Способ №1: Использование возможностей компоновки данных (Рекомендуемый)

Этот способ является наиболее предпочтительным с точки зрения производительности и чистоты кода. Он позволяет объявить поле табличной части доступным для отбора, не связывая таблицы в основном тексте запроса. СУБД будет выполнять соединение только в том случае, если пользователь действительно установил отбор.

Выполним следующие шаги для настройки:

  1. Заимствуем форму списка справочника в расширение.
  2. В свойствах реквизита Список (тип ДинамическийСписок) устанавливаем флаг Произвольный запрос и переходим в конструктор запроса.
  3. Основной текст запроса оставляем максимально простым. Он должен выбирать данные только из основной таблицы справочника, без каких-либо соединений.
  4. В конструкторе запроса переходим на вкладку Компоновка данных.
  5. На вложенной вкладке Таблицы добавляем подчиненную таблицу — табличную часть ДоговорыКонтрагентов.НаправленияДеятельности. Настраиваем ее связь с основной таблицей по полю Ссылка.
  6. Важно: Снимаем флаг Обязательно для этой связи. Это гарантирует, что при пустом отборе левое соединение выполняться не будет, что убережет систему от лишней нагрузки.
  7. На вложенной вкладке Поля добавляем поле НаправлениеДеятельности из нашей табличной части и задаем ему понятный псевдоним, например, НаправлениеДеятельностиДляОтбора.

После выполнения этих настроек поле станет доступно в стандартных настройках отбора динамического списка. Система сама сгенерирует корректный SQL-запрос с группировкой при наложении фильтра.

Способ №2: Использование необязательного условия СКД в тексте запроса

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

Рассмотрим пример правильного построения такого запроса:


ВЫБРАТЬ
    Договоры.Ссылка КАК Ссылка,
    Договоры.Код КАК Код,
    Договоры.Наименование КАК Наименование
ИЗ
    Справочник.ДоговорыКонтрагентов КАК Договоры
{ГДЕ
    Договоры.Ссылка В
        (ВЫБРАТЬ
            ТЧ.Ссылка
        ИЗ
            Справочник.ДоговорыКонтрагентов.НаправленияДеятельности КАК ТЧ
        ГДЕ
            ТЧ.НаправлениеДеятельности = &НаправлениеДеятельности)}

Разберем подробнее, как работает этот механизм. Фигурные скобки {ГДЕ ...} указывают системе компоновки данных, что данное условие является необязательным. Если параметр &НаправлениеДеятельности не передан или отбор не активен, платформа полностью вырежет этот кусок кода при генерации итогового запроса к СУБД.

Если же отбор включен, то сработает условие выбора по множеству В (ВЫБРАТЬ...). Такой подход полностью исключает появление дублирующихся строк в основном списке, так как из подзапроса выбираются только уникальные ссылки на договора.

Программное изменение запроса и установка отбора в расширении

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

Для начала переопределим текст запроса при создании формы на сервере. Для этого заимствуем событие формы ПриСозданииНаСервере и добавим обработчик в расширение в режиме &После:


&После("ПриСозданииНаСервере")
Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
    
    // Формируем новый текст запроса с поддержкой отбора СКД
    НовыйТекстЗапроса = 
    "ВЫБРАТЬ
    |	Договоры.Ссылка КАК Ссылка,
    |	Договоры.Код КАК Код,
    |	Договоры.Наименование КАК Наименование
    |ИЗ
    |	Справочник.ДоговорыКонтрагентов КАК Договоры
    |{ГДЕ
    |	Договоры.Ссылка В
    |		(ВЫБРАТЬ
    |			ТЧ.Ссылка
    |		ИЗ
    |			Справочник.ДоговорыКонтрагентов.НаправленияДеятельности КАК ТЧ
    |		ГДЕ
    |			ТЧ.НаправлениеДеятельности = &НаправлениеДеятельности)}";
    
    Список.ТекстЗапроса = НовыйТекстЗапроса;
    
КонецПроцедуры

Обратите внимание: мы используем аннотацию &После. Это более безопасно, чем &Перед, так как мы гарантируем, что сначала отработает вся базовая логика инициализации формы из основной конфигурации, а уже затем мы аккуратно скорректируем текст запроса.

Теперь добавим на форму списка поле ввода (например, реквизит формы ОтборНаправлениеДеятельности с типом СправочникСсылка.НаправленияДеятельности). Нам необходимо написать обработчик события ПриИзменении для этого поля.

Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), то задача решается вызовом одной типовой процедуры в клиентском контексте:


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

Если же разработка ведется на "чистой" платформе без использования БСП, нам потребуется написать собственный метод работы с компоновщиком настроек динамического списка на сервере:


&НаКлиенте
Процедура ОтборНаправлениеДеятельностиПриИзмененииБезБСП(Элемент)
    
    УстановитьОтборНаСервере(ОтборНаправлениеДеятельности);
    
КонецПроцедуры

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

Важные технические нюансы при работе в Расширениях

При реализации данного механизма помните о следующих важных правилах:

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

← На главную