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