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