В практике разработки на платформе 1С часто возникает задача организации связи «многие ко многим» (M2M) между двумя независимыми справочниками. Ситуация усложняется, когда необходимо не просто зафиксировать связь, но и отслеживать ее изменение во времени. Мы должны точно знать, какие элементы первого справочника были связаны с элементами второго в любой произвольный момент времени в прошлом, настоящем или будущем.
Разберем, как правильно спроектировать структуру данных для такой задачи, почему стандартные решения могут казаться «корявыми» и как реализовать элегантный механизм управления историчностью связей.
Рассмотрим ситуацию: у нас есть Справочник1 и Справочник2. Одному элементу первого справочника могут соответствовать несколько элементов второго, и наоборот. Самый эффективный способ реализации такой связи в 1С — это использование Регистра сведений.
Проанализируем требования к регистру:
Посмотрим на типичную ошибку начинающих разработчиков: попытку создать ресурс «Дата окончания актуальности». Это приводит к избыточности и сложности при записи данных (необходимость корректировать предыдущие записи). Вместо этого мы воспользуемся встроенным механизмом периодичности 1С.
Для решения задачи нам достаточно одного периодического регистра сведений. Рассмотрим его оптимальную структуру:
Измерения:
ОбъектСвязи1 (Тип: СправочникСсылка.Справочник1) — установите свойство Ведущее, если жизненный цикл связи неразрывно связан с этим справочником.ОбъектСвязи2 (Тип: СправочникСсылка.Справочник2) — также рекомендуется индексировать это поле для быстрого поиска «обратных» связей.Ресурсы:
Активность (Тип: Булево) — флаг, указывающий, установлена связь (Истина) или расторгнута (Ложь).Периодичность: установите значение «В пределах секунды» (или «День», если точность до секунд не требуется).
Выясним, как правильно записывать данные, чтобы не возникало противоречий. Главная концепция периодического регистра в 1С заключается в том, что запись считается актуальной до тех пор, пока не появится новая запись с теми же значениями измерений, но более поздним периодом.
Проанализируем ситуацию на примере. Допустим, Элемент_А из первого справочника связан с Элементом_Д и Элементом_Е из второго справочника.
{Период: 01.01.23, Объект1: А, Объект2: Д, Активность: Истина}{Период: 02.01.23, Объект1: А, Объект2: Е, Активность: Истина}Теперь представим, что 03.01.2023 нам нужно «перекинуть» связь: вместо пары А — Е должна появиться пара Б — Е. В этот момент мы должны выполнить два действия в одной транзакции:
Для этого мы добавим в регистр две записи с периодом 03.01.2023:
// Запись для прекращения старой связи
Запись1 = РегистрыСведений.СвязиОбъектов.СоздатьМенеджерЗаписи();
Запись1.Период = Дата("20230103");
Запись1.ОбъектСвязи1 = Справочники.Справочник1.НайтиПоНаименованию("А");
Запись1.ОбъектСвязи2 = Справочники.Справочник2.НайтиПоНаименованию("Е");
Запись1.Активность = Ложь;
Запись1.Записать();
// Запись для создания новой связи
Запись2 = РегистрыСведений.СвязиОбъектов.СоздатьМенеджерЗаписи();
Запись2.Период = Дата("20230103");
Запись2.ОбъектСвязи1 = Справочники.Справочник1.НайтиПоНаименованию("Б");
Запись2.ОбъектСвязи2 = Справочники.Sправочник2.НайтиПоНаименованию("Е");
Запись2.Активность = Истина;
Запись2.Записать();
Для получения списка активных связей на определенный момент времени мы должны использовать виртуальную таблицу СрезПоследних. Однако важно помнить, что СрезПоследних вернет последнюю запись по каждой комбинации измерений, включая те, где Активность = Ложь.
Рассмотрим пример запроса для получения всех активных связей для конкретного элемента первого справочника:
ВЫБРАТЬ
СвязиСрезПоследних.ОбъектСвязи2 КАК СвязанныйЭлемент
ИЗ
РегистрСведений.СвязиОбъектов.СрезПоследних(&Период, ОбъектСвязи1 = &ВыбранныйОбъект) КАК СвязиСрезПоследних
ГДЕ
СвязиСрезПоследних.Активность = ИСТИНА
Важный момент: если вы забудете условие ГДЕ Активность = ИСТИНА, запрос вернет в том числе и разорванные связи, что приведет к логической ошибке в учете.
Поскольку связь «многие ко многим» предполагает поиск в обоих направлениях, проанализируем работу индексов. По умолчанию платформа создает индекс по полям: Период + Измерение1 + Измерение2. Это позволяет мгновенно получать данные в разрезе ОбъектаСвязи1.
Если в вашей задаче так же часто требуется получать данные «наоборот» (какие элементы первого справочника относятся к элементу второго), то для ОбъектаСвязи2 в конфигураторе необходимо установить свойство Индексировать. Это создаст дополнительный индекс и обеспечит высокую скорость работы отчетов и форм при больших объемах данных.
Для обеспечения строгой последовательности и возможности отмены действий рекомендуется делать такой регистр подчиненным регистратору. В этом случае документ (например, «Изменение связей объектов») будет выступать гарантом того, что записи о прекращении одной связи и начале другой будут сделаны одновременно. Это исключает ситуацию «битых» данных, когда одна запись создана, а вторая — нет.
В модуле проведения документа мы можем использовать следующий подход:
Процедура ОбработкаПроведения(Отказ, Режим)
// Очищаем старые записи документа
Движения.СвязиОбъектов.Записывать = Истина;
Движения.СвязиОбъектов.Очистить();
// Добавляем запись о прекращении (если указан объект для отвязки)
Если ЗначениеЗаполнено(ОбъектДляОтвязки) Тогда
Движение = Движения.СвязиОбъектов.Добавить();
Движение.Период = ДатаДокумента;
Движение.ОбъектСвязи1 = ОбъектДляОтвязки;
Движение.ОбъектСвязи2 = ТекущийОбъект2;
Движение.Активность = Ложь;
КонецЕсли;
// Добавляем запись о новой связи
Движение = Движения.СвязиОбъектов.Добавить();
Движение.Период = ДатаДокумента;
Движение.ОбъектСвязи1 = НовыйОбъект1;
Движение.ОбъектСвязи2 = ТекущийОбъект2;
Движение.Активность = Истина;
КонецПроцедуры
Мы рассмотрели наиболее надежный и производительный способ реализации периодической связи «многие ко многим» в 1С. Использование одного периодического регистра сведений с булевым флагом активности позволяет избежать избыточного кода, корректно хранить историю и быстро извлекать данные с помощью виртуальных таблиц. Помните о важности индексирования измерений и использования условий в запросах к срезу последних.