Как связать два справочника в 1С при отношении «многие ко многим» и настроить отборы?

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

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

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

Проблема использования подчиненного справочника

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

Если одна и та же категория (например, «Эконом») должна относиться и к типу «Аренда», и к типу «Продажа», нам придется создать два абсолютно одинаковых элемента в справочнике категорий — по одному для каждого владельца. Рассмотрим, к каким проблемам это приводит:

  1. Дублирование данных. База данных наполняется одинаковыми по смыслу элементами.
  2. Сложность аналитики. В отчетах по продажам или KPI одна и та же категория будет фигурировать как разные сущности. Чтобы сгруппировать данные по категории «Эконом», разработчику придется писать сложные запросы с поиском по наименованию или создавать дополнительные реквизиты сопоставления.
  3. Обновление данных. Если у категории изменится описание или другие свойства, нам придется менять их во всех дублирующих элементах.

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

Почему табличная часть в справочнике — тоже плохое решение?

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

Идеальное решение: Независимые справочники и Регистр сведений

Единственным методологически верным решением в экосистеме 1С для связи «многие ко многим» является использование двух независимых справочников и связующего Регистра сведений. Рассмотрим по шагам, как спроектировать эту структуру:

  1. Создаем независимый справочник Типы.
  2. Создаем независимый справочник Категории (без указания владельца).
  3. Создаем независимый регистр сведений СвязиТиповИКатегорий.

Настроим структуру нашего регистра сведений:

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

Реализация динамического отбора на форме документа

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

Для этого мы откажемся от стандартной связи параметров выбора и настроим фильтрацию программно с помощью обработки событий формы. Рассмотрим два эффективных способа.

Способ 1. Фильтрация через событие «НачалоВыбора»

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

Разберем программный код для модуля формы документа:


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

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

Способ 2. Использование события «ОбработкаПолученияДанныхВыбора»

Если пользователи часто вводят наименование категории с клавиатуры (используют автоподбор), предыдущий способ не сработает в полной мере. Лучше переопределить получение данных выбора непосредственно в модуле менеджера справочника Категории.

Для этого в свойствах реквизита Категория в документе настроим Связи параметров выбора: укажем, что параметр Отбор.ТипСвязи очищается при изменении и зависит от реквизита Тип.

Затем в модуле менеджера справочника Категории напишем следующий код:


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

Построение отчетов и расчет KPI в СКД

При использовании регистра сведений расчет KPI становится прозрачным и легко поддается оптимизации. Рассмотрим, как составить запрос в Системе компоновки данных (СКД), чтобы соединить данные документов продаж с актуальными коэффициентами на дату проведения документа.

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


ВЫБРАТЬ
    Продажи.Ссылка КАК ДокументПродажи,
    Продажи.Дата КАК ДатаДокумента,
    Продажи.Тип КАК Тип,
    Продажи.Категория КАК Категория,
    Продажи.Сумма КАК СуммаПродаж,
    ЕСТЬNULL(Коэффициенты.Коэффициент, 1) КАК КоэффициентKPI,
    Продажи.Сумма * ЕСТЬNULL(Коэффициенты.Коэффициент, 1) КАК СуммаСКоэффициентом
ИЗ
    Документ.Продажи КАК Продажи
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СвязиТиповИКатегорий.СрезПоследних(
        &Период,
        (Тип, Категория) В (
            ВЫБРАТЬ ИЗХ.Тип, ИЗХ.Категория ИЗ Документ.Продажи КАК ИЗХ
        )
    ) КАК Коэффициенты
    ПО Продажи.Тип = Коэффициенты.Тип
    И Продажи.Категория = Коэффициенты.Категория

В СКД для корректного среза последних на каждую дату документа рекомендуется использовать соединение по реальной таблице регистра сведений с условием на период, либо использовать возможности механизма виртуальных таблиц СКД (автоматическую связь наборов данных по периоду). Использование конструкции ЕСТЬNULL гарантирует, что если для типа не задана категория (или нет коэффициента в регистре), система подставит дефолтное значение (например, 1), и расчет не прервется из-за пустых значений (NULL).

Выводы и рекомендации

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

  1. Откажитесь от подчиненных справочников. Это спасет вашу базу данных от лавинообразного дублирования информации и путаницы в аналитических отчетах.
  2. Не используйте табличные части справочников для хранения связей. Они не поддерживают версионирование (историю изменений во времени) и обладают низкой производительностью при выборках в запросах.
  3. Используйте связку «Независимые справочники + Регистр сведений». Это золотой стандарт разработки в 1С. Данный подход гарантирует высокую скорость работы системы, простоту хранения сопутствующей информации (коэффициенты, цены, настройки) с привязкой к датам, а также максимальную гибкость при построении управленческой отчетности и расчете сложных KPI.
← На главную