Как в СКД сгруппировать данные по парам значений одного документа без изменения запроса?

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

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

Классический пример — анализ изменения оценок качества (NPS). Сначала робот фиксирует балл клиента (например, 6), а затем оператор после звонка ставит уточненную оценку (например, 10). В результате в выборке для одного документа образуются строки 6 и 10. В итоговом отчете требуется получить свод вида: 6 — 10: 4 документа, 6 — 8: 1 документ, при этом сам исходный текст запроса менять нельзя (например, запрос зашит в закрытый модуль типовой конфигурации или общий источник данных).

Проанализируем технические особенности платформы и подробно разберем все способы реализации такой задачи средствами СКД.

Ограничения платформы СКД при работе со строками

Прежде чем перейти к решениям, выясним причину сложности:

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

Способ 1. Использование арифметического пользовательского поля (для числовых значений)

Если анализируемые показатели являются небольшими целыми числами (например, оценки от 1 до 10), применим экспресс-метод объединения значений через вычисляемое или пользовательское поле без пересборки структуры схемы.

Разберем по шагам алгоритм настройки:

  1. Перейдем на вкладку Пользовательские поля схемы компоновки или настроек отчета.
  2. Создадим пользовательское поле-выражение с формулой смещения разрядов. Например, если максимальное значение не превышает 10, запишем выражение: 100 * Значение1 + Значение2 (или при агрегации по документу 100 * Минимум(Значение) + Максимум(Значение)).
  3. В структуре отчета добавим группировку по созданному пользовательскому полю, а в ресурсах укажем Количество(Различные Документ).

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

Способ 2. Связи наборов данных (самосоединение в СКД)

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

  1. В схеме компоновки данных добавим второй набор данных-запрос (дублирующий исходный запрос) или скопируем существующий, дав им имена НаборДанных1 и НаборДанных2.
  2. Во втором наборе переименуем поле Значение в ЗначениеПосле, чтобы поля не конфликтовали в структуре.
  3. Перейдем на вкладку Связи наборов данных и свяжем наборы:
    
    Источник связи: НаборДанных1
    Приемник связи: НаборДанных2
    Условие связи: Документ = Документ
    
  4. Для исключения дублирования строк наложим ограничение на связь или добавим параметр/отбор, например: Значение <> ЗначениеПосле (или по номеру этапа/времени фиксации).
  5. На вкладке Настройки построим структуру отчета: добавим группировку сразу по двум полям — Значение и ЗначениеПосле.
  6. В ресурсах отчета укажем расчет количества документов: Количество(Различные Документ)

В результате СКД развернет строки каждого документа в пару колонок, что позволит стандартно группировать итоговую таблицу по любым типам данных (числам, строкам, ссылкам).

Способ 3. Вызов функции общего модуля в вычисляемых полях

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

Разберем пример реализации функции:


// Функция возвращает строковое представление пары значений документа
Функция ПолучитьПаруЗначений(ДокументСсылка) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Таблица.Значение КАК Значение
        |ИЗ
        |   Документ.ОпросКлиента.Оценки КАК Таблица
        |ГДЕ
        |   Таблица.Ссылка = &ДокументСсылка
        |УПОРЯДОЧИТЬ ПО
        |   Таблица.НомерСтроки";
        
    Запрос.УстановитьПараметр("ДокументСсылка", ДокументСсылка);
    Выборка = Запрос.Выполнить().Выбрать();
    
    МассивЗначений = Новый Массив;
    Пока Выборка.Следующий() Цикл
        МассивЗначений.Добавить(Строка(Выборка.Значение));
    КонецЦикла;
    
    Возврат СтрСоединить(МассивЗначений, " -> ");
    
КонецФункции

Далее настроим СКД:

  1. На вкладке Вычисляемые поля добавим поле ПараЗначений с выражением: ИмяОбщегоМодуля.ПолучитьПаруЗначений(Документ).
  2. На вкладке Ресурсы для поля Документ укажем выражение: Количество(Различные Документ).
  3. В настройках отчета создадим группировку по полю ПараЗначений и выведем ресурс.

Способ 4. Программная обработка через внешний набор данных

Если отчет формируется программно, обработаем таблицу значений перед передачей в процессор компоновки данных. Посмотрим на алгоритм:


// 1. Выгружаем результат исходного запроса в ТаблицуЗначений
ТЗ_Исходная = СхемаКомпоновкиДанных.НаборыДанных.НаборДанных1.Запрос; // либо выгрузка результата запроса

// 2. Сворачиваем строки по документу во временную структуру
ТЗ_Результат = Новый ТаблицаЗначений;
ТЗ_Результат.Колонки.Добавить("Документ", Новый ОписаниеТипов("ДокументСсылка.Опрос"));
ТЗ_Результат.Колонки.Добавить("Значение1", Новый ОписаниеТипов("Число"));
ТЗ_Результат.Колонки.Добавить("Значение2", Новый ОписаниеТипов("Число"));

// Заполняем ТЗ_Результат парами значений по каждому документу...

// 3. Передаем подготовленную таблицу в СКД через внешние наборы
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаПар", ТЗ_Результат);

ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);

Этот подход гарантирует высокую производительность на больших объемах данных, полностью освобождая компоновку данных от сложных трансформаций исходных строк.

← На главную