При активном использовании расширений конфигурации 1С программисты часто сталкиваются с необходимостью кастомизации стандартных списков. Типичный сценарий: в расширении к существующему справочнику (например, ДоговорыКонтрагентов) добавляется новая табличная часть (например, НаправленияДеятельности). После этого перед нами встает задача вывести отбор по этой новой табличной части непосредственно на форму списка. При этом менять саму основную конфигурацию нам нельзя.
В этой статье мы подробно разберем, как заставить динамический список (ДС) правильно отрабатывать данный отбор, избежав при этом типичных ошибок производительности и дублирования строк.
Первое побуждение любого разработчика при решении такой задачи — заимствовать форму списка в расширение, открыть конструктор запроса динамического списка и добавить ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) справочника с его новой табличной частью по полю Ссылка. Однако делать этого категорически нельзя.
Проанализируем ситуацию: у одного договора может быть несколько направлений деятельности (несколько строк в табличной части). Если мы свяжем таблицы через левое соединение, СУБД вернет столько строк для каждого договора, сколько элементов указано в его табличной части. В результате в динамическом списке один и тот же договор визуально продублируется.
Более того, если у вашего динамического списка заполнено свойство Основная таблица (а это необходимо для стандартного функционирования списка: добавления, удаления, изменения элементов), платформа 1С при формировании списка выдаст критическую ошибку:
«Обнаружено дублирование ключевых значений в колонках: "Ссылка". Отображение данных в списке невозможно».
Это происходит из-за того, что динамический список жестко требует уникальности ключевого поля (в данном случае ссылки на элемент справочника) для позиционирования курсора, динамического считывания порций данных и корректной прокрутки.
Выясним, какими путями мы можем решить эту задачу элегантно и без ошибок.
Этот способ является наиболее предпочтительным с точки зрения производительности и чистоты кода. Он позволяет объявить поле табличной части доступным для отбора, не связывая таблицы в основном тексте запроса. СУБД будет выполнять соединение только в том случае, если пользователь действительно установил отбор.
Выполним следующие шаги для настройки:
Список (тип ДинамическийСписок) устанавливаем флаг Произвольный запрос и переходим в конструктор запроса.ДоговорыКонтрагентов.НаправленияДеятельности. Настраиваем ее связь с основной таблицей по полю Ссылка.НаправлениеДеятельности из нашей табличной части и задаем ему понятный псевдоним, например, НаправлениеДеятельностиДляОтбора.После выполнения этих настроек поле станет доступно в стандартных настройках отбора динамического списка. Система сама сгенерирует корректный SQL-запрос с группировкой при наложении фильтра.
Если по каким-то причинам нам требуется динамически переопределять текст запроса в коде модуля формы (например, при создании формы на сервере), мы можем использовать конструкцию СКД с фигурными скобками и подзапросом.
Рассмотрим пример правильного построения такого запроса:
ВЫБРАТЬ
Договоры.Ссылка КАК Ссылка,
Договоры.Код КАК Код,
Договоры.Наименование КАК Наименование
ИЗ
Справочник.ДоговорыКонтрагентов КАК Договоры
{ГДЕ
Договоры.Ссылка В
(ВЫБРАТЬ
ТЧ.Ссылка
ИЗ
Справочник.ДоговорыКонтрагентов.НаправленияДеятельности КАК ТЧ
ГДЕ
ТЧ.НаправлениеДеятельности = &НаправлениеДеятельности)}
Разберем подробнее, как работает этот механизм. Фигурные скобки {ГДЕ ...} указывают системе компоновки данных, что данное условие является необязательным. Если параметр &НаправлениеДеятельности не передан или отбор не активен, платформа полностью вырежет этот кусок кода при генерации итогового запроса к СУБД.
Если же отбор включен, то сработает условие выбора по множеству В (ВЫБРАТЬ...). Такой подход полностью исключает появление дублирующихся строк в основном списке, так как из подзапроса выбираются только уникальные ссылки на договора.
Посмотрим на практический пример реализации в коде расширения. Нам нужно решить две задачи: программно подменить текст запроса при открытии формы и настроить реакцию списка на изменение значения в поле выбора на форме.
Для начала переопределим текст запроса при создании формы на сервере. Для этого заимствуем событие формы ПриСозданииНаСервере и добавим обработчик в расширение в режиме &После:
&После("ПриСозданииНаСервере")
Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
// Формируем новый текст запроса с поддержкой отбора СКД
НовыйТекстЗапроса =
"ВЫБРАТЬ
| Договоры.Ссылка КАК Ссылка,
| Договоры.Код КАК Код,
| Договоры.Наименование КАК Наименование
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|{ГДЕ
| Договоры.Ссылка В
| (ВЫБРАТЬ
| ТЧ.Ссылка
| ИЗ
| Справочник.ДоговорыКонтрагентов.НаправленияДеятельности КАК ТЧ
| ГДЕ
| ТЧ.НаправлениеДеятельности = &НаправлениеДеятельности)}";
Список.ТекстЗапроса = НовыйТекстЗапроса;
КонецПроцедуры
Обратите внимание: мы используем аннотацию &После. Это более безопасно, чем &Перед, так как мы гарантируем, что сначала отработает вся базовая логика инициализации формы из основной конфигурации, а уже затем мы аккуратно скорректируем текст запроса.
Теперь добавим на форму списка поле ввода (например, реквизит формы ОтборНаправлениеДеятельности с типом СправочникСсылка.НаправленияДеятельности). Нам необходимо написать обработчик события ПриИзменении для этого поля.
Если в вашей конфигурации используется Библиотека стандартных подсистем (БСП), то задача решается вызовом одной типовой процедуры в клиентском контексте:
&НаКлиенте
Процедура ОтборНаправлениеДеятельностиПриИзменении(Элемент)
// Используем стандартный метод БСП для установки отбора
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
Список,
"НаправлениеДеятельности",
ОтборНаправлениеДеятельности,
ВидСравненияКомпоновкиДанных.Равно,
,
ЗначениеЗаполнено(ОтборНаправлениеДеятельности)
);
КонецПроцедуры
Если же разработка ведется на "чистой" платформе без использования БСП, нам потребуется написать собственный метод работы с компоновщиком настроек динамического списка на сервере:
&НаКлиенте
Процедура ОтборНаправлениеДеятельностиПриИзмененииБезБСП(Элемент)
УстановитьОтборНаСервере(ОтборНаправлениеДеятельности);
КонецПроцедуры
&НаСервере
Процедура УстановитьОтборНаСервере(ЗначениеОтбора)
ГруппаОтбора = Список.КомпоновщикНастроек.ФиксированныеНастройки.Отбор;
ПолеОтбора = Новый ПолеКомпоновкиДанных("НаправлениеДеятельности");
// Поиск существующего элемента отбора для предотвращения дублирования
ЭлементОтбора = Неопределено;
Для Каждого Элемент Из ГруппаОтбора.Элементы Цикл
Если Элемент.ЛевоеЗначение = ПолеОтбора Тогда
ЭлементОтбора = Элемент;
Прервать;
КонецЕсли;
КонецЦикла;
// Если отбор еще не создавался, добавляем его в коллекцию
Если ЭлементОтбора = Неопределено Тогда
ЭлементОтбора = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = ПолеОтбора;
КонецЕсли;
// Заполняем параметры отбора
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = ЗначениеОтбора;
ЭлементОтбора.Использование = ЗначениеЗаполнено(ЗначениеОтбора);
КонецПроцедуры
При реализации данного механизма помните о следующих важных правилах:
Список заимствован корректно, и его тип полностью совпадает с типом в основной конфигурации.Список.ТекстЗапроса, убедитесь, что из запроса не пропали поля, которые используются в условном оформлении формы в основной конфигурации. В противном случае платформа выбросит ошибку при открытии формы.НаправлениеДеятельности в нашей новой табличной части в расширении.Используя предложенные методы, мы получаем стабильное, быстрое и полностью поддерживаемое решение для фильтрации данных по подчиненным таблицам прямо из расширения конфигурации.