Урок 06 · 12 мин чтения

Time intelligence одним набором (calculation group)

Главное применение calculation groups — собрать весь time intelligence (YTD, PY, YOY, rolling) в один переключаемый набор вместо десятков мер. Плюс вторая группа для отклонений и сравнение с произвольным периодом.

В прошлом уроке мы разобрали механизм calculation groups. Теперь — его главное, «убийственное» применение: time intelligence. Именно временные расчёты плодят больше всего почти одинаковых мер, и именно их выгоднее всего собрать в один набор. Опираемся на time intelligence calc group в SQLBI.

На каком примере

Базовые меры [Выручка], [Маржа], [Количество] и помеченная как таблица дат Календарь (как в уроке про календарь, pro-курс). На них построим один TI-набор.

Почему именно TI

Без calculation groups каждый временной вариант пишут отдельной мерой — и для каждой базовой меры:

Выручка YTD, Выручка PY, Выручка YOY, Выручка YOY %, Выручка PYTD …
Маржа YTD,   Маржа PY,   Маржа YOY,   …
Количество YTD, …

3 базовые меры × ~10 временных вариантов = 30 мер с дублированной логикой. Добавили четвёртую базовую — пишите ещё 10. Это не масштабируется. TI-calculation group решает ровно это: логика времени описывается один раз и применяется к любой мере.

TI-набор пунктов

Создаём группу «Время» и наполняем её пунктами. Внутри каждого текущая мера обозначается SELECTEDMEASURE() (прошлый урок):

Текущий  = SELECTEDMEASURE ()

YTD      = CALCULATE ( SELECTEDMEASURE (), DATESYTD ( Календарь[Дата] ) )
QTD      = CALCULATE ( SELECTEDMEASURE (), DATESQTD ( Календарь[Дата] ) )
MTD      = CALCULATE ( SELECTEDMEASURE (), DATESMTD ( Календарь[Дата] ) )

PY       = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( Календарь[Дата] ) )
PY YTD   = CALCULATE ( SELECTEDMEASURE (),
               DATESYTD ( SAMEPERIODLASTYEAR ( Календарь[Дата] ) ) )

YOY      =
    VAR _Cur = SELECTEDMEASURE ()
    VAR _Py  = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( Календарь[Дата] ) )
    RETURN _Cur - _Py

YOY %    =
    VAR _Cur = SELECTEDMEASURE ()
    VAR _Py  = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( Календарь[Дата] ) )
    RETURN DIVIDE ( _Cur - _Py, _Py )

Скользящие 12 мес = CALCULATE ( SELECTEDMEASURE (),
    DATESINPERIOD ( Календарь[Дата], MAX ( Календарь[Дата] ), -12, MONTH ) )

Один набор из ~10 пунктов — и любая мера получает все эти варианты. Положите пункты группы в столбцы матрицы, базовую меру — в значения: на пересечении Power BI применит нужный пункт к выбранной мере.

Почему это и есть «киллер-фича» CG

SELECTEDMEASURE() делает пункт независимым от конкретной меры: «PY YTD» одинаково оборачивает и выручку, и маржу, и количество. Поэтому 10 пунктов закрывают временные варианты для всех базовых мер сразу, а новая базовая мера автоматически получает весь TI-набор — без единой новой меры. Ни один другой приём так не сворачивает дублирование.

Две детали, без которых не работает

1. Помеченная таблица дат. Все функции DATESYTD/SAMEPERIODLASTYEAR/… требуют корректную таблицу дат, помеченную Mark as date table, с непрерывным диапазоном (урок про календарь в pro-курсе). Без неё time intelligence молча врёт или ломается.

2. Динамический формат для процентов. Пункты YTD, PY наследуют формат базовой меры (рубли). А YOY % — это проценты, и формат надо переопределить выражением формата у пункта:

-- Format String Expression для пункта «YOY %»
"0.0%"

Так [Выручка] в пункте «Текущий» покажется «1 234 ₽», а в «YOY %» — «12,3%». Это та самая dynamic format string из прошлого урока, и для TI-набора она обязательна.

Сортировка пунктов

Пункты по умолчанию идут по алфавиту — «MTD, PY, YTD» вперемешку. Задайте каждому Ordinal (порядковый номер), чтобы в матрице они шли логично: Текущий → YTD → QTD → MTD → PY → PY YTD → YOY → YOY %. Сортировка пунктов — отдельный столбец порядка, ровно как «Месяц по номеру месяца».

Вторая группа: отклонения поверх первой

В TI-наборе выше «YOY» и «YOY %» вшиты прямо в пункты периода. Но отклонение к прошлому году нужно ведь не только для «Текущего», а для любого периода: «YTD к ПГ, %», «скользящие 12М, Δ абс». Вшивать вариант сравнения в каждый период — снова комбинаторный взрыв (период × способ сравнения). Решение — вторая calculation group, которая работает поверх первой.

Делаем чистое разделение. Первая группа «Период» отвечает только за то, какой период показать (Текущий, YTD, QTD, MTD, PY, скользящие 12М — без YOY). Вторая группа «Сравнение» отвечает за то, как сравнить:

-- Группа «Сравнение»
Значение     = SELECTEDMEASURE ()

Δ к ПГ, абс  =
    SELECTEDMEASURE ()
        - CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( Календарь[Дата] ) )

Рост к ПГ, % =
    VAR _Cur = SELECTEDMEASURE ()
    VAR _Ref = CALCULATE ( SELECTEDMEASURE (), SAMEPERIODLASTYEAR ( Календарь[Дата] ) )
    RETURN DIVIDE ( _Cur - _Ref, _Ref )

Теперь это две оси в матрице: «Период» (Текущий / YTD / …) и «Сравнение» (Значение / Δ абс / Рост %). На пересечении «YTD × Рост к ПГ, %» вы получите рост YTD к аналогичному YTD прошлого года — без отдельной меры под эту комбинацию.

Почему это работает — composition через precedence

Когда применяются две группы, их SELECTEDMEASURE() вкладываются друг в друга, а порядок вложения задаётся свойством Precedence: группа с бо́льшим precedence применяется первой (внутренней). Ставим «Период» выше «Сравнения» — тогда сначала к базовой мере применяется период (например, YTD), а «Сравнение» оборачивает уже преобразованную меру. Поэтому в пункте «Рост к ПГ, %» и SELECTEDMEASURE(), и его сдвиг на год считаются в YTD-контексте — выходит честный «YTD vs PY YTD». Одна и та же группа сравнения работает поверх любого периода.

Платёж за это — настоящий: P периодов × C способов сравнения комбинаций при P + C пунктах в сумме. Добавили новый период — он сразу получает все сравнения; добавили новое сравнение — оно сразу работает для всех периодов.

Сравнение с произвольным периодом (отвязанный календарь)

Прошлый год — частный случай. Бизнесу часто нужно сравнить текущие продажи с любым периодом: «как сейчас против прошлой акции», «против лучшего квартала». Жёстко в DAX такой период не зашьёшь — его должен выбирать пользователь. Приём: второй, отвязанный календарь.

Заводим таблицу КалендарьСравнения с теми же датами, но без связи с фактом (отдельный «остров данных», как disconnected-параметр из урока про what-if). Пользователь выбирает в её срезе диапазон-эталон. Срез не фильтрует продажи напрямую — мы читаем выбор через MIN/MAX и применяем его руками к настоящему Календарю:

-- пункты в группе «Сравнение»
Δ к выбранному, абс =
    VAR _От  = MIN ( КалендарьСравнения[Дата] )
    VAR _До  = MAX ( КалендарьСравнения[Дата] )
    VAR _Cur = SELECTEDMEASURE ()
    VAR _Ref =
        CALCULATE (
            SELECTEDMEASURE (),
            REMOVEFILTERS ( Календарь ),
            Календарь[Дата] >= _От && Календарь[Дата] <= _До
        )
    RETURN _Cur - _Ref

Рост к выбранному, % =
    VAR _От  = MIN ( КалендарьСравнения[Дата] )
    VAR _До  = MAX ( КалендарьСравнения[Дата] )
    VAR _Cur = SELECTEDMEASURE ()
    VAR _Ref =
        CALCULATE (
            SELECTEDMEASURE (),
            REMOVEFILTERS ( Календарь ),
            Календарь[Дата] >= _От && Календарь[Дата] <= _До
        )
    RETURN DIVIDE ( _Cur - _Ref, _Ref )
Зачем отвязанный календарь и REMOVEFILTERS

Связанный календарь сразу бы отфильтровал продажи — и «эталон» совпал бы с текущим периодом. Отвязанная таблица этого не делает: её выбор живёт сам по себе, а мы достаём его границы (MIN/MAX) и строим эталонный контекст вручную. REMOVEFILTERS ( Календарь ) обязателен — он снимает фильтр текущего периода, иначе пересечение с выбранным диапазоном даст пусто. Так _Ref — это мера за произвольный, выбранный пользователем период.

Точно так же это composition с группой «Период»: при «YTD × Рост к выбранному, %» сравнение применится поверх YTD-контекста. А если выбор должен быть произвольным набором дат (не сплошным диапазоном), вместо >= … && <= … берут TREATAS ( VALUES ( КалендарьСравнения[Дата] ), Календарь[Дата] ) — перенос выбранных дат на реальный календарь (урок про виртуальные связи, TREATAS).

Грабли TI-набора
  • Применяется ко всему. Пункт обернёт любую меру, попавшую в визуал, даже ту, где время бессмысленно (например, «средняя цена» или текстовая мера). При необходимости защищайтесь проверкой ISSELECTEDMEASURE или возвратом SELECTEDMEASURE() без обёртки.
  • Несколько групп → precedence. Как только появляется вторая группа («Сравнение» выше, «Валюта» и т.п.), порядок их вложения задаётся свойством Precedence. Перепутаете — и «YTD × Рост %» посчитается не от того периода. Правило: группа с бо́льшим precedence применяется первой (внутренней).
  • Нет таблицы дат — нет TI. Самая частая причина «пустого PY»: таблица дат не помечена или с дырами.

Что дальше

Весь time intelligence теперь живёт в одном наборе и применяется к любой мере. Дальше — выход за пределы одного файла: модели на SSAS / Analysis Services Tabular. Следующий урок.

Почему time intelligence — главное применение calculation groups?
Именно временные расчёты дают комбинаторный взрыв мер (база × варианты времени). TI-calculation group описывает каждый временной сдвиг один раз через SELECTEDMEASURE(), и любая мера — текущая и будущая — автоматически получает весь набор YTD/PY/YOY/rolling. Это снимает дублирование, которого больше всего именно в TI.
Прогресс сохраняется в вашем браузере.
§ Power BI под ключ

Нужно внедрить
это в компании?

Соберём DWH, модель и дашборды под ваши данные. Бесплатная консультация — 30 минут.

Телефон+7 918 042 34 43