Как сгруппировать колонки в СКД по порядковому номеру продажи клиента?

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

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

Если мы попытаемся сделать это напрямую, выбрав в качестве группировки колонок поле ссылки на документ (например, ДокументРеализации), то получим на выходе так называемую «шахматку». Поскольку каждый документ уникален, для каждого клиента будет создана своя собственная колонка. В результате отчет растянется на тысячи столбцов, большая часть ячеек которых будет пустой. Наша задача — сгруппировать данные так, чтобы показатели распределялись строго по колонкам «Продажа №1», «Продажа №2», «Продажа №3», независимо от того, какими именно документами они были оформлены.

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

Почему системные поля СКД не подходят для решения задачи?

Первое, что приходит на ум начинающему разработчику, — использовать системное поле СКД НомерПоПорядкуВГруппировке. Однако этот подход заведомо ошибочен. Давайте проанализируем причину.

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

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


Вариант 1. Классическая нумерация в запросе через самосоединение

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

Рассмотрим по шагам, как реализовать этот алгоритм в тексте запроса схемы компоновки данных:

  1. Сначала выберем все уникальные продажи в разрезе контрагентов и поместим их во временную таблицу. Это необходимо, чтобы исключить дублирование записей при расчете.
  2. Затем выполним левое соединение этой таблицы с самой собой по совпадению контрагента и по условию, что дата документа из второй таблицы меньше или равна дате из первой таблицы.
  3. Выполним группировку по ключевым полям первой таблицы, а количество уникальных документов из второй таблицы и будет являться порядковым номером сделки.

Посмотрим на пример реализации такого запроса:


ВЫБРАТЬ
    РеализацияТоваровУслуг.Контрагент КАК Контрагент,
    РеализацияТоваровУслуг.Ссылка КАК Ссылка,
    РеализацияТоваровУслуг.МоментВремени КАК МоментВремени,
    РеализацияТоваровУслуг.СуммаДокумента КАК Сумма
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
    Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ГДЕ
    РеализацияТоваровУслуг.Проведен = ИСТИНА
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т1.Контрагент КАК Контрагент,
    Т1.Ссылка КАК Ссылка,
    Т1.Сумма КАК Сумма,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т2.Ссылка) КАК ПорядковыйНомер
ИЗ
    ВТ_Продажи КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Продажи КАК Т2 
        ПО Т1.Контрагент = Т2.Контрагент 
        И Т1.МоментВремени >= Т2.МоментВремени
ГРУППИРОВАТЬ ПО
    Т1.Контрагент,
    Т1.Ссылка,
    Т1.Сумма

Преимущества метода: Работает на любых версиях платформы 1С:Предприятие 8, не требует написания программного кода на встроенном языке, полностью декларативен.

Недостатки метода: Крайне низкая производительность на больших базах данных. Так как используется соединение «каждый с каждым» в рамках одного контрагента, сложность алгоритма возрастает квадратично ($O(N^2)$). Если у одного контрагента будет несколько тысяч продаж, отчет может вызвать перегрузку процессора или аварийное завершение работы СУБД.


Вариант 2. Высокопроизводительный запрос с использованием функции АВТОНОМЕРЗАПИСИ()

Если вы работаете на современной версии платформы (начиная с 8.3), то можете использовать мощный инструмент оптимизации — функцию АВТОНОМЕРЗАПИСИ() во временных таблицах. Этот способ позволяет пронумеровать записи за линейное время ($O(N)$), избегая тяжелых соединений.

Давайте разберем этот продвинутый алгоритм по шагам:

  1. Выбираем документы продаж, упорядочивая их по Контрагент и МоментВремени. Это критически важно: документы одного клиента должны идти строго друг за другом в хронологическом порядке. Результат помещаем во временную таблицу ВТ_Исходная.
  2. Выбираем данные из ВТ_Исходная в новую таблицу ВТ_С_Номерами и добавляем поле с функцией АВТОНОМЕРЗАПИСИ() КАК ГлобальныйНомер. Поскольку исходная таблица была упорядочена, документы получат сквозную нумерацию, сохраняя хронологию по каждому контрагенту.
  3. Создаем временную таблицу ВТ_Минимумы. В ней группируем данные по контрагенту и находим минимальный глобальный номер для каждого из них: МИНИМУМ(ГлобальныйНомер) КАК МинимальныйНомерВГруппе.
  4. В финальном запросе соединяем таблицы ВТ_С_Номерами и ВТ_Минимумы по полю Контрагент. Порядковый номер продажи внутри группы каждого контрагента вычисляем по простой математической формуле: ГлобальныйНомер - МинимальныйНомерВГруппе + 1.

Проанализируем пример практической реализации данного запроса:


ВЫБРАТЬ
    Реализация.Контрагент КАК Контрагент,
    Реализация.Ссылка КАК Ссылка,
    Реализация.СуммаДокумента КАК Сумма
ПОМЕСТИТЬ ВТ_Исходная
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Проведен = ИСТИНА
УПОРЯДОЧИТЬ ПО
    Контрагент,
    Реализация.МоментВремени
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    АВТОНОМЕРЗАПИСИ() КАК ГлобальныйНомер,
    Т_Исходная.Контрагент КАК Контрагент,
    Т_Исходная.Ссылка КАК Ссылка,
    Т_Исходная.Сумма КАК Сумма
ПОМЕСТИТЬ ВТ_С_Номерами
ИЗ
    ВТ_Исходная КАК Т_Исходная
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т_Номера.Контрагент КАК Контрагент,
    МИНИМУМ(Т_Номера.ГлобальныйНомер) КАК МинимальныйНомерВГруппе
ПОМЕСТИТЬ ВТ_Минимумы
ИЗ
    ВТ_С_Номерами КАК Т_Номера
ГРУППИРОВАТЬ ПО
    Т_Номера.Контрагент
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т_Номера.Контрагент КАК Контрагент,
    Т_Номера.Ссылка КАК Ссылка,
    Т_Номера.Сумма КАК Сумма,
    Т_Номера.ГлобальныйНомер - Т_Минимумы.МинимальныйНомерВГруппе + 1 КАК ПорядковыйНомер
ИЗ
    ВТ_С_Номерами КАК Т_Номера
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Минимумы КАК Т_Минимумы 
        ПО Т_Номера.Контрагент = Т_Минимумы.Контрагент

Преимущества метода: Потрясающая скорость работы на больших объемах данных. Время выполнения запроса растет линейно, что позволяет без проблем обрабатывать миллионы документов продаж.


Вариант 3. Использование внешнего набора данных (программный метод)

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

Выясним причину эффективности этого метода. Нам гораздо проще обойти выборку записей в обычном цикле на встроенном языке 1С и последовательно увеличивать счетчик, нежели пытаться построить громоздкие запросы.

Выполним следующие шаги для реализации программного метода:

  1. В схеме компоновки данных создадим Набор данных - объект с именем НаборДанныхОбъект и добавим в него поля: Контрагент (тип Ссылка), Ссылка (тип Ссылка), Сумма (тип Число) и ПорядковыйНомер (тип Число).
  2. В модуле отчета перед формированием выполним запрос для получения сырых данных, отсортированных по контрагенту и дате документа.
  3. Выгрузим результат в таблицу значений и программно пронумеруем строки в цикле.
  4. Передадим заполненную таблицу значений в компоновщик макета СКД.

Посмотрим на пример программного кода в модуле отчета:


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

Настройка структуры и красивое оформление колонок в СКД

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

Выполним настройку структуры отчета:

  1. Перейдем на вкладку Настройки в конструкторе СКД.
  2. Добавим структуру типа Таблица.
  3. В Строки таблицы добавим группировку по полю Контрагент.
  4. В Колонки таблицы добавим группировку по полю ПорядковыйНомер.
  5. В Выбранные поля добавим ресурс, который мы хотим анализировать (например, Сумма или количество проданного товара).

Если мы оставим все как есть, в шапке колонок пользователь увидит простые числа: «1», «2», «3» и так далее. Это выглядит не слишком информативно. Чтобы исправить это и придать отчету профессиональный вид, воспользуемся механизмом выражений представления:

  1. Перейдем на вкладку Наборы данных.
  2. Найдем в списке полей наше поле ПорядковыйНомер.
  3. В колонке Выражение представления напишем следующую строку:
    "Сделка № " + Формат(ПорядковыйНомер, "ЧГ=0")

Теперь система автоматически подставит красивое текстовое представление в шапку каждой колонки, и пользователь увидит надписи «Сделка № 1», «Сделка № 2» вместо безликих цифр.

Важная рекомендация по оптимизации интерфейса: Если у ваших постоянных клиентов количество сделок исчисляется сотнями, то отчет снова может разрастись в ширину. Чтобы избежать этого, рекомендуется ограничить вывод колонок, например, показывать только первые 10 сделок. Для этого в настройках отчета (на вкладке «Отбор» для группировки колонок) установите простое условие: ПорядковыйНомер Меньше или равно 10. Все последующие продажи будут аккуратно скрыты, а отчет останется компактным и удобным для чтения.

Мы успешно решили задачу группировки колонок СКД по порядковому номеру события! Использование этих подходов позволит вам создавать быстрые, интерактивные и визуально понятные аналитические отчеты в среде 1С.

← На главную