При разработке отчетов на платформе 1С:Предприятие 8.3 с использованием Системы Компоновки Данных (СКД) часто возникает задача вывода нестандартных итогов в группировках. Это может быть не простая сумма или количество, а, например, преобразованное значение длительности (годы, месяцы, дни), рассчитанный показатель или какой-либо специфический агрегат, который невозможно получить стандартными функциями СКД.
Рассмотрим, как мы можем подойти к решению этой задачи, основываясь на опыте коллег и рекомендациях по работе с СКД. Основная сложность заключается в том, чтобы заставить СКД отображать заранее подготовленные данные в качестве итогов, а не рассчитывать их самостоятельно.
Первоначальная проблема часто кроется не в настройках самой СКД, а в запросе, который формирует исходные данные. Если в запросе допущена ошибка (например, отсутствует необходимое соединение таблиц, как это было в обсуждаемом случае), СКД просто не получит полные или корректные данные для расчета, или же получит их в искаженном виде. Первым делом, всегда тщательно проверяйте запрос, лежащий в основе набора данных СКД.
Один из самых эффективных способов вывода нестандартных итогов — это включение этих итогов непосредственно в набор данных с помощью оператора UNION ALL. Этот подход позволяет передать в СКД как детальные строки, так и специально подготовленные итоговые строки, которые СКД затем просто отобразит, вместо того чтобы пытаться агрегировать их.
Разберем этот подход по шагам:
ВидСтроки = "Детализация". Это поле поможет нам в СКД отличать детальные записи от итоговых.
ВЫБРАТЬ
ТаблицаДеталей.ФизическоеЛицо КАК ФизическоеЛицо,
ТаблицаДеталей.ДатаНачала КАК ДатаНачала,
ТаблицаДеталей.ДатаОкончания КАК ДатаОкончания,
РАЗНОСТЬДАТ(ТаблицаДеталей.ДатаНачала, ТаблицаДеталей.ДатаОкончания, ДЕНЬ) КАК ДлительностьВДнях,
ТаблицаДеталей.ВидСтажа КАК ВидСтажа,
"Детализация" КАК ВидСтроки,
1 КАК НомерПоПорядку // Произвольный номер, не -1 для деталей
ИЗ
РегистрСведений.ДанныеСтажа КАК ТаблицаДеталей
ДатаНачала, ДатаОкончания), можем использовать NULL или пустые значения, а для итогового значения — наше рассчитанное значение. Важно также добавить служебное поле ВидСтроки = "Итог" и другой маркер для НомерПоПорядку, например, -1, как это было предложено в исходной теме.
ВЫБРАТЬ
ТаблицаИтогов.ФизическоеЛицо КАК ФизическоеЛицо,
NULL КАК ДатаНачала,
NULL КАК ДатаОкончания,
NULL КАК ДлительностьВДнях,
ТаблицаИтогов.ВидСтажа КАК ВидСтажа, // Может быть также NULL, если итог по всем видам стажа
<ВашРасчетДлительностиВФорматеГодыМесяцыДни> КАК ДлительностьПриведенная,
"Итог" КАК ВидСтроки,
-1 КАК НомерПоПорядку // Маркер для итоговой строки
ИЗ
(ВЫБРАТЬ
ДанныеСтажа.ФизическоеЛицо КАК ФизическоеЛицо,
ДанныеСтажа.ВидСтажа КАК ВидСтажа,
// Здесь сложная логика расчета общего стажа в нужном формате
// Пример: ВЫРАЖЕНИЕ("ПРЕДСТАВЛЕНИЕ(КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&НачалоПериода, МЕСЯЦ, СУММА(ДанныеСтажа.Месяцев)), ДЕНЬ) - &НачалоПериода)" КАК ДлительностьПриведенная),
// более реалистично - вызов функции или сложный набор DATEDIFF
// Пусть для примера это будет просто строка
"5 лет 3 месяца 10 дней" КАК ДлительностьПриведенная
ИЗ
РегистрСведений.ДанныеСтажа КАК ДанныеСтажа
СГРУППИРОВАТЬ ПО
ДанныеСтажа.ФизическоеЛицо,
ДанныеСтажа.ВидСтажа
) КАК ТаблицаИтогов
UNION ALL. Важно, чтобы количество и типы полей в обоих запросах совпадали, даже если для некоторых полей в итоговом запросе мы используем NULL.
ВЫБРАТЬ
ТаблицаДеталей.ФизическоеЛицо КАК ФизическоеЛицо,
ТаблицаДеталей.ДатаНачала КАК ДатаНачала,
ТаблицаДеталей.ДатаОкончания КАК ДатаОкончания,
ТаблицаДеталей.ДлительностьВДнях КАК ДлительностьВДнях,
NULL КАК ДлительностьПриведенная, // Для детальных строк это поле будет пусто
ТаблицаДеталей.ВидСтажа КАК ВидСтажа,
ТаблицаДеталей.ВидСтроки КАК ВидСтроки,
ТаблицаДеталей.НомерПоПорядку КАК НомерПоПорядку
ИЗ
(ВЫБРАТЬ
ДанныеСтажа.ФизическоеЛицо,
ДанныеСтажа.ДатаНачала,
ДанныеСтажа.ДатаОкончания,
РАЗНОСТЬДАТ(ДанныеСтажа.ДатаНачала, ДанныеСтажа.ДатаОкончания, ДЕНЬ) КАК ДлительностьВДнях,
ДанныеСтажа.ВидСтажа,
"Детализация" КАК ВидСтроки,
1 КАК НомерПоПорядку
ИЗ
РегистрСведений.ДанныеСтажа КАК ДанныеСтажа
) КАК ТаблицаДеталей
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТаблицаИтогов.ФизическоеЛицо,
NULL,
NULL,
NULL,
ТаблицаИтогов.ДлительностьПриведенная КАК ДлительностьПриведенная,
ТаблицаИтогов.ВидСтажа,
ТаблицаИтогов.ВидСтроки,
ТаблицаИтогов.НомерПоПорядку
ИЗ
(ВЫБРАТЬ
ДанныеСтажа.ФизическоеЛицо КАК ФизическоеЛицо,
ДанныеСтажа.ВидСтажа КАК ВидСтажа,
"5 лет 3 месяца 10 дней" КАК ДлительностьПриведенная, // Ваш сложный расчет здесь
"Итог" КАК ВидСтроки,
-1 КАК НомерПоПорядку
ИЗ
РегистрСведений.ДанныеСтажа КАК ДанныеСтажа
СГРУППИРОВАТЬ ПО
ДанныеСтажа.ФизическоеЛицо,
ДанныеСтажа.ВидСтажа
) КАК ТаблицаИтогов
ДатаНачала, ДатаОкончания), установите фильтр по полю ВидСтроки со значением "Детализация". Это гарантирует, что в этих группировках не будут отображаться итоговые строки.ДлительностьПриведенная), в основной группировке (например, по ФизическоеЛицо) задайте выражение для расчета. Это выражение должно извлекать значение из итоговой строки. Например, мы можем использовать агрегатную функцию Максимум() в сочетании с условным оператором:
Максимум(Выбор Когда ВидСтроки = "Итог" Тогда ДлительностьПриведенная Иначе NULL Конец)
ДлительностьПриведенная только из тех строк, где ВидСтроки равен "Итог", и проигнорирует остальные. Таким образом, в строке итогов группировки по ФизическоеЛицо будет отображаться наше заранее рассчитанное значение.НомерПоПорядку используется корректно в сортировках, чтобы итоговая строка по группировке отображалась в нужном месте (обычно в конце).Этот подход позволяет СКД работать с уже "готовыми" итогами, избегая сложностей с переопределением стандартной логики агрегации.
Изначально автор темы пытался использовать условное оформление, но без успеха. В целом, условное оформление позволяет изменять внешний вид или видимость элементов, но не переопределять значение ресурса. Однако, если наша задача состоит в том, чтобы показать одно значение в строке детализации, а другое — в строке итога, или скрыть/показать что-то, условное оформление может быть полезно в сочетании с фильтрацией.
Концепция из Сообщения 2 предлагает:
ДатаНачала, ДатаОкончания) установить фильтр, чтобы исключить записи, помеченные как итоговые (например, НомерПоПорядку <> -1). Таким образом, эти группировки будут показывать только детальные данные.ФизическоеЛицо) для вывода итога необходимо получить запись, где НомерПоПорядку = -1, и вывести соответствующее значение. Это уже сложнее сделать без UNION, так как СКД по умолчанию будет пытаться агрегировать все доступные записи в группировке. Без заранее "вложенной" итоговой строки (как в случае с UNION), СКД не будет "знать", какое значение ей нужно взять в качестве нестандартного итога.Таким образом, этот подход, хотя и кажется интуитивным, без предварительной подготовки данных (например, через UNION) оказывается сложным для реализации, поскольку СКД в своих итогах работает со значениями, присутствующими в самой группировке, а не с отдельными "итоговыми" записями, которые нужно "найти". Тем не менее, условное оформление может быть использовано для стилизации этих итоговых строк, когда они уже корректно отображаются.
Иногда, когда стандартные настройки СКД не позволяют добиться требуемого результата или он слишком сложен для реализации через выражения и фильтры, на помощь приходят собственные макеты СКД. Хотя автор изначально хотел их избежать, многие опытные разработчики подтверждают, что создание собственного макета часто занимает меньше времени, чем попытки "обмануть" стандартные механизмы СКД.
Когда стоит использовать собственные макеты:
Как использовать собственные макеты:
ФизическоеЛицо, мы можем создать "Макет итога группировки 'ФизическоеЛицо'".[ДлительностьПриведенная].UNION, как мы рассмотрели выше), вы просто указываете его в макете.Макеты дают полный контроль над визуальным представлением данных и могут значительно упростить вывод сложных нестандартных итогов, если их предварительный расчет уже выполнен в запросе.
Помимо основных методов, обсужденных на форуме, существуют и другие, более продвинутые подходы, которые мы можем рассмотреть для вывода нестандартных итогов:
// Пример вычисляемого поля
ВЫБОР
КОГДА (РассчитанныйПоказатель > 100) ТОГДА "Высокий"
ИНАЧЕ "Низкий"
КОНЕЦ
Сумма, Количество и т.д.) и задать свою логику, используя поля текущей группировки. Например, мы можем использовать условные операторы или математические функции для получения требуемого значения. Это отличается от вычисляемого поля тем, что выражение применяется именно к агрегации. Например, можно использовать функции типа ВычислитьВыражениеВГруппировке("Максимум(МоеПоле)", "Родитель") для получения значения из родительской группировки.ПриПолученииДанных, которые мы можем использовать для модификации данных или итогов перед их выводом в отчет. Например, в обработчике события ПриПолученииДанных мы можем изменить значения в строках набора данных или добавить новые строки, уже содержащие рассчитанные итоги.
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// Пример программного обхода или модификации данных
// СтандартнаяОбработка = Ложь; // Если хотим полностью перехватить вывод
// Получить данные, изменить, вывести
КонецПроцедуры
Процедура ПриПолученииДанныхНабораДанных(Элемент, Расшифровка, СтандартнаяОбработка)
// В этом событии можно модифицировать каждую строку набора данных
// Например, добавить новое поле или изменить существующее для нужных строк
Для Каждого СтрокаТЗ Из Элемент.Выгрузить() Цикл
Если СтрокаТЗ.НекоеУсловие Тогда
СтрокаТЗ.ПолеДляИтога = "СформированныйИтог";
КонецЕсли;
КонецЦикла;
КонецПроцедуры
UNION необходимо учитывать производительность. Если расчеты замедляют формирование отчета, стоит рассмотреть перенос части логики в запрос (что часто бывает быстрее) или оптимизацию алгоритмов расчета нестандартных итогов. Мы всегда должны стремиться к тому, чтобы основные вычисления выполнялись на стороне СУБД, а не в СКД или программном коде 1С, так как это обычно более эффективно.В заключение, вывод нестандартных итогов в группировках СКД — это распространенная, но решаемая задача. Как мы выяснили из обсуждения и дополнительных сведений, чаще всего проблема решается коррекцией запроса и использованием UNION ALL для включения итогов в набор данных. Если этот подход не подходит, мы всегда можем обратиться к гибкости пользовательских макетов или даже к программному формированию отчета для достижения желаемого результата.