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

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

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

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

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

Проанализируем архитектурные особенности платформы 1С, с которыми сталкивается разработчик при работе с динамическими списками:

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

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

Способ 1. Денормализация данных (хранение подготовленной строки)

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

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

  1. Добавим в справочник (например, Контрагенты) реквизит ПредставлениеТелефонов с типом Строка.
  2. В модуле объекта в событии ПередЗаписью или в подписке на событие записи контактной информации программно сформируем итоговую строку с помощью функции СтрСоединить.
  3. В произвольном запросе динамического списка считываем уже готовое реквизитное поле ПредставлениеТелефонов без дополнительных соединений.

Посмотрим на пример кода формирования строки в модуле объекта:


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

КонецПроцедуры

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

Способ 2. Левое соединение по фиксированным видам контактной информации

Если количество видов телефонов заранее известно и ограничено (например: Мобильный телефон, Рабочий телефон, Факс), мы можем выполнить отдельное ЛЕВОЕ СОЕДИНЕНИЕ к регистру сведений для каждого вида контактной информации.

Проанализируем пример запроса, где телефоны склеиваются в секции ВЫБРАТЬ с помощью оператора +, функции ЕСТЬNULL и конструкции ВЫБОР КОГДА для корректного размещения разделителей:


ВЫБРАТЬ
    Контрагенты.Ссылка КАК Ссылка,
    Контрагенты.Наименование КАК Наименование,
    ЕСТЬNULL(ТелМобильный.НомерТелефона, "") 
    + ВЫБОР 
        КОГДА ТелМобильный.НомерТелефона ЕСТЬ НЕ NULL И ТелРабочий.НомерТелефона ЕСТЬ НЕ NULL 
        ТОГДА ", " 
        ИНАЧЕ "" 
    КОНЕЦ 
    + ЕСТЬNULL(ТелРабочий.НомерТелефона, "") КАК ВсеТелефоны
ИЗ
    Справочник.Контрагенты КАК Контрагенты
    
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК ТелМобильный
        ПО ТелМобильный.Объект = Контрагенты.Ссылка
        И ТелМобильный.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.ТелефонМобильный)
        
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КонтактнаяИнформация КАК ТелРабочий
        ПО ТелРабочий.Объект = Контрагенты.Ссылка
        И ТелРабочий.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.ТелефонРабочий)

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

Недостатки: Подходит только тогда, когда виды телефонов строго фиксированы, а их количество невелико.

Способ 3. Замена динамического списка на таблицу значений

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

Разберем алгоритм решения:

  1. На форме создается реквизит типа ТаблицаЗначений и соответствующая ему таблица формы.
  2. Выборка данных выполняется обычным запросом, получающим контрагентов и их контактную информацию.
  3. Выборка группируется программно в коде с помощью функции СтрСоединить или обхода результатов запроса.
  4. Сформированная таблица выгружается на форму.

Рассмотрим пример программной обработки выборки на сервере:


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

КонецПроцедуры

Итоги

Выбор конкретного решения зависит от требований к конфигурации:

← На главную