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

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

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

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

Разбор типичных ошибок: почему нельзя использовать запросы в цикле

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


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

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

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

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

Решение 1. Произвольный запрос в Динамическом списке (Классический метод)

Самый простой и быстрый способ вывести данные табличной части в список — перевести основной реквизит формы списка (динамический список) в режим использования произвольного запроса.

Рассмотрим по шагам, как это сделать:

  1. Откроем форму списка нужного справочника (например, Номенклатура) в конфигураторе.
  2. В окне свойств реквизита Список (тип ДинамическийСписок) установим флаг Произвольный запрос.
  3. Нажмем на ссылку Настройка списка и перейдем в конструктор запроса.

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

Запишем текст запроса:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    Номенклатура.Код КАК Код,
    Номенклатура.Наименование КАК Наименование,
    ЕСТЬNULL(НоменклатураТовары.Количество, 0) КАК Количество,
    Номенклатура.Склад КАК Склад
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура.Товары КАК НоменклатураТовары
        ПО Номенклатура.Ссылка = НоменклатураТовары.Ссылка
        И НоменклатураТовары.НомерСтроки = 1

Проанализируем структуру этого запроса подробнее:

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

Решение 2. Агрегирование данных (Если строк в табличной части несколько)

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

Рассмотрим пример запроса с вложенной выборкой и агрегацией данных:


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    Номенклатура.Код КАК Код,
    Номенклатура.Наименование КАК Наименование,
    ЕСТЬNULL(ТоварыИтоги.ОбщееКоличество, 0) КАК Количество
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            Товары.Ссылка КАК Ссылка,
            СУММА(Товары.Количество) КАК ОбщееКоличество
        ИЗ
            Справочник.Номенклатура.Товары КАК Товары
        ГРУППИРОВАТЬ ПО
            Товары.Ссылка
    ) КАК ТоварыИтоги
        ПО Номенклатура.Ссылка = ТоварыИтоги.Ссылка

В этом варианте мы сначала во вложенном запросе группируем всю таблицу товаров по ссылкам и рассчитываем сумму количества для каждого элемента, а уже затем полученную свернутую таблицу соединяем с основным справочником. Это решает проблему дублирования строк в списке.

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

Описанные выше способы отлично работают на небольших базах данных. Однако при росте объема информации (когда количество элементов справочника и строк табличных частей превысит десятки тысяч) левые соединения начнут сильно замедлять работу интерфейса.

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

Решение 3. Использование события "ПриПолученииДанныхНаСервере"

Начиная с версии платформы 1С 8.3.10, у динамического списка появилось замечательное событие — ПриПолученииДанныхНаСервере. Оно позволяет динамически подгружать дополнительные данные только для тех строк списка, которые в данный момент реально отображаются на экране пользователя (обычно это всего 30–40 строк).

Разберем, как реализовать этот подход по шагам:

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

В модуле формы пишем следующий программный код:


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

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

Решение 4. Денормализация данных (Архитектурно самый надежный способ)

Если вам требуется не только видеть количество в списке, но и иметь возможность быстро сортировать по нему список или строить отборы, использование динамических вычислений или соединений в реальном времени не подойдет. В таких случаях применяется классический паттерн проектирования БД — денормализация.

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

Выполним следующие шаги:

  1. Создадим в справочнике Номенклатура обычный реквизит КоличествоДляСписка (тип Число).
  2. В модуле объекта справочника пропишем автоматическое заполнение этого реквизита при записи объекта:

Процедура ПередЗаписью(Отказ)
    
    // Защита от авто-регистрации обмена, если данные не поменялись
    Если Товары.Количество() > 0 Тогда
        НовоеКоличество = Товары[0].Количество;
    Else
        НовоеКоличество = 0;
    КонецЕсли;
    
    Если КоличествоДляСписка <> НовоеКоличество Тогда
        КоличествоДляСписка = НовоеКоличество;
    КонецЕсли;
    
КонецПроцедуры

При таком подходе данные рассчитываются ровно один раз — в момент нажатия кнопки "Записать". В форме списка вам останется лишь вывести обычное поле КоличествоДляСписка. Это решение обеспечивает максимальное быстродействие, легкую настройку любых отборов и не требует написания сложного программного кода в интерфейсных формах.

← На главную