В процессе разработки в среде 1С:Предприятие часто возникает задача отображения данных из подчиненных таблиц (табличных частей документов или справочников) в плоских списках — динамических списках на управляемых формах. Классический пример: в форме списка документов ПоступлениеБезналичныхДенежныхСредств необходимо отобразить колонку Партнер, которая хранится не в шапке, а в строках табличной части РасшифровкаПлатежа.
В этой статье мы подробно разберем, как решить эту задачу, рассмотрим различные варианты реализации, начиная с простых запросов и заканчивая архитектурно правильными методами, гарантирующими высокую производительность системы даже на крупных базах данных.
Прежде чем перейти к написанию кода, проанализируем ситуацию. В документе ПоступлениеБезналичныхДенежныхСредств реквизит Партнер расположен внутри табличной части РасшифровкаПлатежа. По условиям бизнес-логики, в данном документе табличная часть практически всегда содержит только одну строку, представляющую собой аналитику платежа. Наша задача — "вытащить" значение партнера из этой единственной строки и отобразить его в общем списке документов.
Поскольку форма списка построена на базе механизма Динамический список, стандартного обращения через точку к реквизитам табличной части (как к реквизитам шапки) у нас нет. Нам потребуется изменить схему запроса динамического списка.
---
Самый очевидный и быстрый способ, предложенный участниками обсуждения, — это перевести динамический список в режим использования произвольного запроса и связать основную таблицу документа с таблицей его табличной части посредством левого соединения (LEFT JOIN).
Для реализации этого способа выполним следующие шаги:
Список (тип: ДинамическийСписок).Рассмотрим по шагам оптимальный вариант запроса без использования лишних вложенных запросов и параметров:
ВЫБРАТЬ
ДанныеДокумента.Ссылка КАК Ссылка,
ДанныеДокумента.ПометкаУдаления КАК ПометкаУдаления,
ДанныеДокумента.Номер КАК Номер,
ДанныеДокумента.Дата КАК Дата,
ДанныеДокумента.Проведен КАК Проведен,
ДанныеДокумента.Организация КАК Организация,
ДанныеДокумента.СуммаДокумента КАК СуммаДокумента,
ДанныеДокумента.Валюта КАК Валюта,
// Выбираем реквизит Партнер из присоединенной табличной части
ЕСТЬNULL(ТабЧастьРасшифровка.Партнер, ЗНАЧЕНИЕ(Справочник.Партнеры.ПустаяСсылка)) КАК Партнер
ИЗ
Документ.ПоступлениеБезналичныхДенежныхСредств КАК ДанныеДокумента
// Соединяем документ с его табличной частью по Ссылке и жесткому условию на первую строку
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеБезналичныхДенежныхСредств.РасшифровкаПлатежа КАК ТабЧастьРасшифровка
ПО ДанныеДокумента.Ссылка = ТабЧастьРасшифровка.Ссылка
И ТабЧастьРасшифровка.НомерСтроки = 1
Важные особенности этого запроса:
ТабЧастьРасшифровка.НомерСтроки = 1. Платформа 1С гарантирует, что при записи документа строки его табличной части всегда нумеруются последовательно, начиная с единицы. Таким образом, мы всегда выбираем именно первую строку.ЕСТЬNULL необходима на случай, если табличная часть документа окажется пустой (например, в непроведенном черновике). Без этой функции в списке отобразится пустое значение неопределенного типа, что может вызвать ошибки при интерактивной фильтрации по этой колонке.---
Хотя первый способ прост в реализации, рассмотрим подробнее, почему опытные разработчики не рекомендуют использовать левые соединения с табличными частями в динамических списках:
Ссылка. Однако составного индекса Ссылка + НомерСтроки в СУБД по умолчанию нет. При росте базы данных (сотни тысяч документов) такое соединение начнет тормозить интерфейс при прокрутке и поиске.---
Выяснив причины возможных проблем производительности при использовании первого способа, разберем архитектурно правильный подход, который рекомендует фирма «1С» для разработки тиражных решений.
Суть метода заключается в денормализации структуры данных: если реквизит табличной части критически важно видеть в списках, сортировать и отбирать по нему документы, его необходимо продублировать в шапку документа.
Выполним задачу по шагам:
ОсновнойПартнер (тип: СправочникСсылка.Партнеры).ПоступлениеБезналичныхДенежныхСредств перейдем в обработчик события ПередЗаписью и добавим код автоматического заполнения этого реквизита:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Заполняем реквизит шапки данными из первой строки табличной части
Если РасшифровкаПлатежа.Количество() > 0 Тогда
ЭтотОбъект.ОсновнойПартнер = РасшифровкаПлатежа[0].Партнер;
Иначе
ЭтотОбъект.ОсновнойПартнер = Справочники.Партнеры.ПустаяСсылка();
КонецЕсли;
КонецПроцедуры
После этого нам достаточно просто вытащить реквизит шапки ОсновнойПартнер на форму списка документов стандартными средствами. Запрос динамического списка останется простым и быстрым, индексация будет работать корректно, а RLS не создаст лишней нагрузки на СУБД.
Обратите внимание: Для существующих в базе документов потребуется написать обработку группового перепроведения или перезаписи, которая заполнит новый реквизит ОсновнойПартнер по историческим данным.---
Если денормализация базы данных по каким-то причинам нежелательна (например, конфигурация находится на поддержке и крайне нежелательно менять структуру таблиц метаданных), а колонка нужна исключительно для визуального отображения пользователю (без необходимости фильтрации и сортировки), рассмотрим современный механизм платформы — событие ПриПолученииДанныхНаСервере.
Этот метод позволяет запрашивать данные из табличной части порционно — только для тех строк документов, которые в данный момент реально отображаются на экране пользователя (обычно около 30–40 строк).
Рассмотрим алгоритм настройки:
ВЫБРАТЬ
ДанныеДокумента.Ссылка,
...
ВЫРАЗИТЬ(НЕОПРЕДЕЛЕНО КАК Справочник.Партнеры) КАК ПартнерОтображение
ИЗ
Документ.ПоступлениеБезналичныхДенежныхСредств КАК ДанныеДокумента
ПриПолученииДанныхНаСервере:
&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Получаем массив ссылок на документы, которые сейчас видны на экране
МассивСсылок = Строки.ПолучитьКлючи();
Если МассивСсылок.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Быстрым запросом выбираем партнеров только по нужным документам
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТабЧасть.Ссылка КАК Ссылка,
| ТабЧасть.Партнер КАК Партнер
|ИЗ
| Документ.ПоступлениеБезналичныхДенежныхСредств.РасшифровкаПлатежа КАК ТабЧасть
|ГДЕ
| ТабЧасть.Ссылка В(&МассивСсылок)
| И ТабЧасть.НомерСтроки = 1";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
Выборка = Запрос.Выполнить().Выбрать();
// Заполняем отображаемое поле в строках динамического списка
Пока Выборка.Следующий() Цикл
ТекущаяСтрока = Строки[Выборка.Ссылка];
ТекущаяСтрока.Данные["ПартнерОтображение"] = Выборка.Партнер;
КонецЦикла;
КонецПроцедуры
Преимущества этого подхода:
Мы проанализировали три метода вывода реквизитов табличной части в динамический список. Для быстрого решения "здесь и сейчас" подойдет Способ 1 с использованием левого соединения. Однако при проектировании высоконагруженных систем предпочтение всегда следует отдавать Способу 2 (денормализация) или Способу 3 (OnGetDataAtServer), так как они гарантируют стабильную и быструю работу интерфейса. Для ознакомления с другими практическими вариантами расширения состава полей можно изучить руководство по методам доработки динамических списков.