Как реализовать единую форму выбора для двух справочников в управляемой форме 1С?

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

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

Попытка решить задачу «в лоб» через произвольный запрос динамического списка с конструкцией ОБЪЕДИНИТЬ сразу приводит к двум критическим проблемам: сильным задержкам при открытии/прокрутке и невозможности штатно спозиционироваться на текущей ссылке. Разберем по шагам, почему это происходит и как правильно решить данную задачу на уровне архитектуры управляемого приложения.

Выясним причину: почему динамический список с объединением не работает как ожидается

Проанализируем механику работы платформы при выполнении запросов динамического списка без указания свойства ОсновнаяТаблица:

  1. Потеря объектного ключа и генерация идентификаторов: когда в динамическом списке не указана основная таблица (а при использовании ОБЪЕДИНИТЬ или вложенных запросов платформа не позволяет ее назначить), поле Ссылка перестает быть уникальным системным ключом строки для таблицы формы. Вместо ссылочного ключа платформа генерирует динамические целочисленные идентификаторы строк по мере загрузки порций данных на клиент. В результате свойство ТекущаяСтрока возвращает не ссылку, а внутреннее число. Попытка на сервере вычислить номер строки перебором всей выборки полностью разрушает концепцию динамического считывания и приводит к тяжелым зависаниям интерфейса.
  2. Деградация пагинации в СУБД: со штатной основной таблицей платформа выполняет быстрое курсорное смещение по кластерному индексу (keyset pagination). Без основной таблицы СУБД вынуждена сканировать весь массив данных с вычислением смещения, что многократно увеличивает нагрузку на сервер баз данных при постраничной подгрузке.
  3. Накладные расходы оператора ОБЪЕДИНИТЬ: использование оператора ОБЪЕДИНИТЬ эквивалентно конструкции UNION в SQL, которая выполняет неявную дедупликацию строк (DISTINCT). Для устранения потенциальных дубликатов СУБД производит сортировку всего результирующего набора во временных таблицах. Чтобы исключить эту фазу, в запросах 1С всегда следует использовать ОБЪЕДИНИТЬ ВСЕ.
  4. Отключение встроенных сервисов: в динамическом списке без основной таблицы перестает работать стандартный контекстный поиск платформы, отключается иерархический просмотр и усложняется корректная работа ограничений на уровне записей (RLS).

Решение 1. Архитектурное: вспомогательный регистр сведений (поисковый индекс)

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

Создадим независимый регистр сведений (например, ИндексПоискаСубъектов) со следующей структурой:

  1. Измерение Наименование: строка нужной длины, индексированная. Служит для упорядочивания и контекстного поиска.
  2. Измерение ИдентификаторПоиска: дополнительные реквизиты поиска (ИНН, артикул, код).
  3. Измерение или ресурс СсылкаОбъекта: составной тип, включающий целевые справочники.

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


ВЫБРАТЬ
    Регистр.Наименование КАК Наименование,
    Регистр.ИдентификаторПоиска КАК ИдентификаторПоиска,
    Регистр.СсылкаОбъекта КАК СсылкаОбъекта
ИЗ
    РегистрСведений.ИндексПоискаСубъектов КАК Регистр

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


Процедура ПриЗаписи(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

    МенеджерЗаписи = РегистрыСведений.ИндексПоискаСубъектов.СоздатьМенеджерЗаписи();
    МенеджерЗаписи.СсылкаОбъекта = ЭтотОбъект.Ссылка;
    МенеджерЗаписи.Наименование = ЭтотОбъект.Наименование;
    МенеджерЗаписи.ИдентификаторПоиска = ЭтотОбъект.ИНН;
    МенеджерЗаписи.Записать(Истина);
КонецПроцедуры

Решение 2. Разделение списков по страницам (вкладкам) формы

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

Разберем по шагам реализацию формы выбора:

  1. Создаем общую форму или форму выбора произвольного справочника. Размещаем на ней группу страниц (вкладок).
  2. На первой странице размещаем таблицу с динамическим списком первого справочника (например, Контрагенты), на второй — таблицу с динамическим списком второго справочника (например, ФизическиеЛица). Для каждого списка явно указываем его основную таблицу.
  3. В модуле формы обрабатываем передачу текущего значения при открытии:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    ТекущееЗначение = Параметры.Значение;
    
    Если ЗначениеЗаполнено(ТекущееЗначение) Тогда
        Если ТипЗнч(ТекущееЗначение) = Тип("СправочникСсылка.Контрагенты") Тогда
            Элементы.ГруппаСтраницы.ТекущаяСтраница = Элементы.СтраницаКонтрагенты;
            Элементы.СписокКонтрагенты.ТекущаяСтрока = ТекущееЗначение;
        ИначеЕсли ТипЗнч(ТекущееЗначение) = Тип("СправочникСсылка.ФизическиеЛица") Тогда
            Элементы.ГруппаСтраницы.ТекущаяСтраница = Элементы.СтраницаФизЛица;
            Элементы.СписокФизЛица.ТекущаяСтрока = ТекущееЗначение;
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

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

Решение 3. Перехват ввода по строке без открытия формы

Во многих сценариях пользователям не требуется визуальный скроллинг всей базы — им достаточно быстро выбрать объект по подстроке. В этом случае эффективнее всего настроить событие АвтоподборТекста или ОкончаниеВводаТекста для элемента формы составного типа.

Посмотрим на пример формирования единого выпадающего списка вариантов в обработчике АвтоподборТекста:


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

&НаКлиенте
Процедура ПолеСоставногоТипаАвтоподбор(Элемент, Текст, ДанныеВыбора, Ожидание, СтандартнаяОбработка)
    Если СтрДлина(СокрЛП(Текст)) < 2 Тогда
        Возврат;
    КонецЕсли;
    
    СтандартнаяОбработка = Ложь;
    ДанныеВыбора = ПолучитьВариантыВыбора(Текст);
КонецПроцедуры

Обратите внимание на использование конструкции ОБЪЕДИНИТЬ ВСЕ и директивы ПЕРВЫЕ. Такой запрос отрабатывает за миллисекунды, а пользователь сразу видит совпадения из обоих справочников прямо под курсором.

Решение 4. Форма поиска по требованию (Search-on-Demand)

Для крупных информационных баз с миллионами записей проектируют диалоговую форму поиска вместо формы классического списка. Изначально таблица формы пуста, что исключает любые задержки при ее открытии. Пользователь указывает искомую строку (наименование, телефон, ИНН), нажимает кнопку «Найти», после чего сервер выполняет быстрый выбор первых 50–100 совпадений в таблицу значений формы.

Выбранный элемент возвращается стандартной процедурой оповещения:


&НаКлиенте
Процедура ТаблицаРезультатовВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
    СтандартнаяОбработка = Ложь;
    ТекущиеДанные = Элементы.ТаблицаРезультатов.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        ОповеститьОВыборе(ТекущиеДанные.Ссылка);
    КонецЕсли;
КонецПроцедуры

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

← На главную