Как вывести нестандартные итоги в группировках Системы Компоновки Данных (СКД) 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

Решение 1: Коррекция запроса источника данных и использование оператора UNION

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

Один из самых эффективных способов вывода нестандартных итогов — это включение этих итогов непосредственно в набор данных с помощью оператора UNION ALL. Этот подход позволяет передать в СКД как детальные строки, так и специально подготовленные итоговые строки, которые СКД затем просто отобразит, вместо того чтобы пытаться агрегировать их.

Разберем этот подход по шагам:

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

Этот подход позволяет СКД работать с уже "готовыми" итогами, избегая сложностей с переопределением стандартной логики агрегации.

Решение 2: Использование условного оформления и фильтрации в СКД (без UNION)

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

Концепция из Сообщения 2 предлагает:

  1. В детальных группировках (например, ДатаНачала, ДатаОкончания) установить фильтр, чтобы исключить записи, помеченные как итоговые (например, НомерПоПорядку <> -1). Таким образом, эти группировки будут показывать только детальные данные.
  2. В родительской группировке (например, ФизическоеЛицо) для вывода итога необходимо получить запись, где НомерПоПорядку = -1, и вывести соответствующее значение. Это уже сложнее сделать без UNION, так как СКД по умолчанию будет пытаться агрегировать все доступные записи в группировке. Без заранее "вложенной" итоговой строки (как в случае с UNION), СКД не будет "знать", какое значение ей нужно взять в качестве нестандартного итога.

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

Решение 3: Применение собственных макетов СКД

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

Когда стоит использовать собственные макеты:

Как использовать собственные макеты:

  1. Перейдите на вкладку "Макеты" в настройках СКД.
  2. Создайте новый макет для нужного типа области: "Макет заголовка", "Макет группировки", "Макет детальных записей", "Макет итога" и т.д. Например, для вывода нестандартного итога в группировке ФизическоеЛицо, мы можем создать "Макет итога группировки 'ФизическоеЛицо'".
  3. В созданном макете нарисуйте нужную вам таблицу, объедините ячейки, задайте шрифты и цвета.
  4. В нужные ячейки вставьте параметры макета. Имена параметров должны совпадать с именами полей набора данных или вычисляемых полей СКД. Например, [ДлительностьПриведенная].
  5. СКД будет использовать ваш макет при формировании отчета, подставляя значения полей в соответствующие параметры. Если ваше нестандартное итоговое значение уже присутствует в наборе данных (например, через UNION, как мы рассмотрели выше), вы просто указываете его в макете.

Макеты дают полный контроль над визуальным представлением данных и могут значительно упростить вывод сложных нестандартных итогов, если их предварительный расчет уже выполнен в запросе.

Дополнительные подходы и нюансы

Помимо основных методов, обсужденных на форуме, существуют и другие, более продвинутые подходы, которые мы можем рассмотреть для вывода нестандартных итогов:

Вычисляемые поля и выражения для итогов

  1. Вычисляемое поле в наборе данных: Мы можем создать вычисляемое поле непосредственно в наборе данных СКД. Это поле будет содержать логику расчета нашего нестандартного итога, используя данные из других полей набора и встроенные функции СКД. Затем это вычисляемое поле можно вывести в качестве ресурса. Этот метод наиболее эффективен, когда расчет может быть выполнен на уровне отдельных записей или простыми агрегатами.
    
    // Пример вычисляемого поля
    ВЫБОР
        КОГДА (РассчитанныйПоказатель > 100) ТОГДА "Высокий"
        ИНАЧЕ "Низкий"
    КОНЕЦ
    
  2. Выражения для итогов ресурсов: В настройках каждого ресурса мы можем задать пользовательское выражение для расчета его итога. Это позволяет переопределить стандартные агрегатные функции (Сумма, Количество и т.д.) и задать свою логику, используя поля текущей группировки. Например, мы можем использовать условные операторы или математические функции для получения требуемого значения. Это отличается от вычисляемого поля тем, что выражение применяется именно к агрегации. Например, можно использовать функции типа ВычислитьВыражениеВГруппировке("Максимум(МоеПоле)", "Родитель") для получения значения из родительской группировки.

Настройки СКД для управления агрегацией

  1. Собственные агрегатные функции: В СКД существует возможность определять собственные агрегатные функции. Это требует некоторого программирования на языке 1С (создание общего модуля с экспортной функцией, которая будет использоваться в выражении СКД) и позволяет реализовать очень сложную логику агрегации, недоступную стандартными средствами. Это решение для крайне специфических и сложных случаев.
  2. Размещение итогов: Важно правильно настроить размещение итогов в СКД. Мы можем указать, для каких группировок выводить итоги, а также настроить их положение (например, "В начале" или "В конце" группировки) и состав полей, которые будут отображаться в итоговой строке.

Программное формирование отчета и события СКД

  1. Обход результата компоновки: Если стандартных настроек и макетов недостаточно, мы можем программно обойти результат компоновки данных. Это дает полный контроль над каждой ячейкой отчета, позволяя вставлять любые вычисленные значения в нужные места. Это самый гибкий, но и самый трудоемкий подход, часто используемый при генерации нестандартных печатных форм, где требуется максимальная кастомизация.
  2. События СКД: В СКД есть события, такие как ПриПолученииДанных, которые мы можем использовать для модификации данных или итогов перед их выводом в отчет. Например, в обработчике события ПриПолученииДанных мы можем изменить значения в строках набора данных или добавить новые строки, уже содержащие рассчитанные итоги.
    
    Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
        // Пример программного обхода или модификации данных
        // СтандартнаяОбработка = Ложь; // Если хотим полностью перехватить вывод
        // Получить данные, изменить, вывести
    КонецПроцедуры
    
    Процедура ПриПолученииДанныхНабораДанных(Элемент, Расшифровка, СтандартнаяОбработка)
        // В этом событии можно модифицировать каждую строку набора данных
        // Например, добавить новое поле или изменить существующее для нужных строк
        Для Каждого СтрокаТЗ Из Элемент.Выгрузить() Цикл
            Если СтрокаТЗ.НекоеУсловие Тогда
                СтрокаТЗ.ПолеДляИтога = "СформированныйИтог";
            КонецЕсли;
        КонецЦикла;
    КонецПроцедуры
    

Отладка СКД и оптимизация производительности

  1. Отладка СКД: Для понимания, почему нестандартные итоги ведут себя не так, как ожидается, полезно использовать инструменты отладки СКД. Просмотр промежуточного результата запроса, структуры сформированного отчета (через кнопку "Результат" на вкладке "Настройки") и значений полей в отладчике помогает определить, на каком этапе происходит ошибка или неверное вычисление. Это позволяет нам отслеживать, как данные преобразуются от запроса до финального отчета.
  2. Оптимизация производительности: При использовании сложных выражений для итогов, подзапросов, временных таблиц или объемных запросов с UNION необходимо учитывать производительность. Если расчеты замедляют формирование отчета, стоит рассмотреть перенос части логики в запрос (что часто бывает быстрее) или оптимизацию алгоритмов расчета нестандартных итогов. Мы всегда должны стремиться к тому, чтобы основные вычисления выполнялись на стороне СУБД, а не в СКД или программном коде 1С, так как это обычно более эффективно.

В заключение, вывод нестандартных итогов в группировках СКД — это распространенная, но решаемая задача. Как мы выяснили из обсуждения и дополнительных сведений, чаще всего проблема решается коррекцией запроса и использованием UNION ALL для включения итогов в набор данных. Если этот подход не подходит, мы всегда можем обратиться к гибкости пользовательских макетов или даже к программному формированию отчета для достижения желаемого результата.

← На главную