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

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

В процессе разработки конфигураций нередко возникает задача организации связи между двумя табличными частями одного документа по принципу «один ко многим» (Master-Detail). Например, в основной табличной части указывается перечень товаров или услуг, а во второй табличной части — их расширенная аналитика, спецификация, серийные номера или распределение по этапам.

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

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

Вариант 1. Использование целочисленного ключа строки (подход типовых конфигураций)

В типовых конфигурациях 1С (ERP, УТ, КА) наиболее распространен подход с созданием числовых реквизитов связи. Добавим в обе табличные части соответствующие реквизиты:

  1. В главной табличной части создадим реквизит ИдентификаторСтроки (или КлючСтроки) с типом Число (длина 10, точность 0, неотрицательное).
  2. В подчиненной табличной части создадим реквизит КлючСвязи с аналогичным типом Число (длина 10, точность 0, неотрицательное).
  3. Для реквизита КлючСвязи подчиненной таблицы в свойствах метаданных обязательно установим свойство «Индексировать», чтобы ускорить соединения таблиц в запросах.

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


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    
    // Определим максимальный существующий ключ в главной таблице
    МаксимальныйКлюч = 0;
    Для Каждого Стр Из Товары Цикл
        Если Стр.ИдентификаторСтроки > МаксимальныйКлюч Тогда
            МаксимальныйКлюч = Стр.ИдентификаторСтроки;
        КонецЕсли;
    КонецЦикла;
    
    // Присвоим уникальные ключи новым строкам
    Для Каждого Стр Из Товары Цикл
        Если Не ЗначениеЗаполнено(Стр.ИдентификаторСтроки) Тогда
            МаксимальныйКлюч = МаксимальныйКлюч + 1;
            Стр.ИдентификаторСтроки = МаксимальныйКлюч;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Числовой ключ компактен, занимает минимальный объем в базе данных и обеспечивает максимальную скорость работы индексов при выполнении запросов.

Вариант 2. Использование строкового уникального идентификатора (GUID)

Второй вариант заключается в использовании строкового представления глобального уникального идентификатора. Для этого создадим:

  1. В главной табличной части — реквизит ИдентификаторСтроки с типом Строка (длина 36 символов, фиксированная длина).
  2. В подчиненной табличной части — реквизит КлючСвязи с типом Строка (длина 36 символов, фиксированная длина) с включенным свойством «Индексировать».

Генерация идентификатора выполняется через вызов функции платформы:

Строка.ИдентификаторСтроки = Строка(Новый УникальныйИдентификатор());

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

Организация связи на управляемой форме (Master-Detail)

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

В модуле формы документа настроим обработчик события ПриАктивизацииСтроки для элемента первой таблицы:


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

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

Подводные камни и их устранение

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

  1. Интерактивное копирование строк (клавиша F9): когда пользователь копирует строку в главной таблице, платформа копирует все значения, включая ключ связи. В результате две разные строки получают одинаковый ключ, и подчиненные записи перемешиваются. Чтобы этого избежать, в событии таблицы формы ПриНачалеРедактирования следует проверять признак ЭтоНоваяСтрока и при наличии заполненного ключа у новой строки генерировать для нее новый ключ.
  2. Каскадное удаление строк: платформа не отслеживает целостность между строками табличных частей. Если пользователь удалит строку из главной таблицы, в подчиненной таблице останутся строки-сироты. Очистку подчиненных строк необходимо выполнять в событии ПередЗаписью модуля объекта:

// Удаление строк-сирот в событии ПередЗаписью
СписокКлючей = Новый Массив;
Для Каждого Стр Из Товары Цикл
    СписокКлючей.Добавить(Стр.ИдентификаторСтроки);
КонецЦикла;

Индекс = СоставСпецификации.Количество() - 1;
Пока Индекс >= 0 Цикл
    СтрПодчиненная = СоставСпецификации[Индекс];
    Если СписокКлючей.Найти(СтрПодчиненная.КлючСвязи) = Неопределено Тогда
        СоставСпецификации.Удалить(СтрПодчиненная);
    КонецЕсли;
    Индекс = Индекс - 1;
КонецЦикла;

Альтернативные архитектурные решения

Если структура данных требует сложного связывания, стоит проанализировать альтернативные варианты:

  1. Подчиненный регистр сведений: вынесение строк детализации в независимый или подчиненный регистратору регистр сведений. Это избавляет документ от хранения избыточных данных и ускоряет его открытие.
  2. Дерево значений на форме: отображение данных на форме через элемент управления ДеревоЗначений с последующей сериализацией в плоские таблицы документа при записи.
  3. Сериализация в JSON: если подчиненные данные нужны только для отображения и внутренней логики (по ним не строятся отчеты и проводки), их можно сохранять в строку JSON непосредственно в реквизит строки основной таблицы.
← На главную