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

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

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

Проблема стандартного подхода: почему зависает интерфейс?

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

Выясним причину этой проблемы. Событие ПриВыводеСтроки вызывается платформой индивидуально для каждой строки, которая в данный момент отрисовывается на экране монитора. Если внутри обработчика этого события написать прямой запрос к базе данных или обратиться к реквизитам через точку (например, написать ОформлениеСтроки.ДанныеСтроки.Ссылка.ДоговорКонтрагента.Владелец.ИНН), система будет вынуждена делать отдельный запрос к СУБД для каждой видимой строки. В результате при выводе 20 строк на экран платформа сделает 20 отдельных обращений к базе данных. В программировании эта проблема широко известна как проблема N+1 запросов.

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

Разбор решения из Сообщения 5: Плюсы и скрытые угрозы полного кэширования

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

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

  1. Зависание при открытии: Если в журнале или списке содержатся десятки или сотни тысяч документов, то выполнение полного запроса ко всей таблице базы данных при открытии формы приведет к тому, что пользователь будет ждать открытия списка несколько минут. Программа просто «повиснет».
  2. Нерациональный расход памяти: Хранение огромной таблицы значений в оперативной памяти клиентского компьютера перегружает рабочую станцию пользователя.
  3. Бессмысленная работа СУБД: Пользователь в 95% случаев открывает список, чтобы найти конкретный документ из последних десяти или применить быстрый отбор. Загружать при этом историю за последние 10 лет абсолютно бессмысленно.

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

Вариант решения №1: Ленивое кэширование (Lazy Loading) в оперативной памяти

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

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

Разберем по шагам реализацию этого механизма на встроенном языке «1С:Предприятие 8»:


Перем КэшДополнительныхДанных; // Объявляем переменную для кэша на уровне модуля формы

Процедура ПриОткрытии()
    // Инициализируем кэш в виде структуры "Соответствие" при открытии формы
    КэшДополнительныхДанных = Новый Соответствие;
КонецПроцедуры

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

Использование Соответствие в качестве структуры кэша обеспечивает практически мгновенный доступ к кэшированным данным по ключу-ссылке. Это гарантирует плавность прокрутки формы списка даже при работе с медленными сетевыми соединениями.

Вариант решения №2: Пакетная выборка данных в один запрос

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

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


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

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

Вариант решения №3: Использование скрытого свойства «ИспользоватьВЗапросе»

Малоизвестная, но очень мощная встроенная возможность платформы обычных форм «1С:Предприятие 8» — это программная настройка свойства колонки источника данных «Использовать в запросе».

Если вам нужно вывести реквизит какого-то связанного объекта (например, ИНН контрагента из шапки документа или артикул номенклатуры), вы можете расширить динамический запрос, который платформа формирует автоматически для отображения списка.

Посмотрим на пример программного добавления такой колонки при инициализации формы:


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

При использовании этого свойства вам не нужно писать код в событиях отрисовки строк. Платформа 1С самостоятельно модифицирует SQL-запрос к СУБД, добавляя левое соединение с таблицей контрагентов, и выбирает ИНН пакетом оптимальным образом.

Резюме и рекомендации

Подведем итоги нашего исследования. Выбор конкретного метода реализации зависит от характера выводимых данных:

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

← На главную