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

Программист 1С v8.3 (Обычные формы)
← На главную

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

Постановка задачи и анализ возможных подходов

Представим, что у нас есть документ Сертификат с табличной частью Вагоны. В этой табличной части присутствуют реквизиты НомерВагона (тип Строка) и Вес (тип Число). Нам требуется вывести на форму списка две колонки: список номеров вагонов через запятую и суммарный вес по документу.

Для реализации этой задачи в обычных формах у разработчика есть два принципиально разных пути:

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

Давайте подробно разберем каждый из этих способов, их преимущества, недостатки и особенности реализации.

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

Этот способ является наиболее простым с точки зрения кода формы списка. Мы добавляем в сам документ Сертификат два новых реквизита шапки: ВесПартии (Число) и СписокНомеровВагонов (Строка неограниченной длины или фиксированной, если мы уверены в лимитах).

Главное правило при таком подходе: заполнение реквизитов шапки должно происходить строго на сервере при записи объекта, чтобы данные всегда оставались актуальными. Нам ни в коем случае нельзя писать этот код только в модуле формы, так как при программном изменении документов из внешних обработок или при обмене данными обработчики формы не сработают, и данные в списке станут неактуальными. Разместим логику в модуле объекта документа.

Разберем по шагам правильную реализацию в процедуре ПередЗаписью модуля объекта:


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

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

Преимущества метода:

Недостатки метода:

Способ 2. Оптимальный динамический вывод через ПриПолученииДанных

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

Выясним причину: событие ПриВыводеСтроки срабатывает отдельно для каждой отображаемой строки. Если на экране выводится 50 строк, система выполнит 50 отдельных запросов к базе данных, что приведет к катастрофическому падению производительности («проблема N+1 запроса»).

Правильным решением для обычных форм является использование события ПриПолученииДанных табличного поля. В этот обработчик передается коллекция ОформленияСтрок, содержащая пакет строк, которые в данный момент реально отображаются на экране пользователя (обычно не более 20–30 строк). Мы можем собрать все ссылки этих документов и получить данные по их табличным частям одним-единственным запросом.

Разберем пример реализации этого обработчика в модуле формы списка документов:


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

Преимущества метода:

Недостатки метода:

Сравнительный анализ решений

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

Если для пользователей критически важна возможность быстрого поиска конкретного вагона непосредственно через строку поиска списка документов или сортировка по весу партии, то вашим выбором должен стать Способ 1 (Денормализация). Наличие реальных полей в базе данных позволит СУБД быстро строить индексы и осуществлять мгновенный поиск.

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

← На главную