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