При использовании DAX аналитикам важно следить не только за корректностью результатов, но и за производительностью системы при обработке запросов. Одним из инструментов повышения эффективности является корректное использование функции SUMMARIZE
. Всем, кто работает с большими объемами данных, активно изучает синтаксис DAX, а также интересующимся особенностями SUMMARIZE
— добро пожаловать под кат!
Начнем с того, что по DAX, хотя и стал популярен благодаря Microsoft, но сегодня этот язык представляет нечто намного большее и используется, в частности, аналитиками на базе платформы Visiology. И хотя существует официальная документация по DAX от Microsoft, неофициальная документация может давать больше информации. В частности, я активно пользуюсь dax.guide и sqlbi.com. Кстати, при ее изучении вы сможете понять, почему в Visiology вводится поддержка не всех форматов синтаксиса. Как минимум, некоторые из приемов MS Power BI лучше вообще не использовать.
Скажите «Нет» expressions в SUMMARIZE
Это справедливо для работы с SUMMARIZE
. В приведенных выше источниках говорят не только о преимуществах его использования, но также о проблемах производительности при расчете выражения в SUMMARIZE
, о depricated-параметрах имени выражения <Name>
и, собственно, самого выражения <Expression>
.
Действительно, раз профессионалы говорят, что это deprecated, то и не нужно пользоваться функцией для этих целей. К тому же для расчета выражений, как известно, существует функция SUMMARIZECOLUMNS. Так зачем делать то, что работает хуже?
Visiology расширяет синтаксис поддерживаемого DAX в соответствии с запросами пользователей. И поэтому в версии DAX от Visiology просто не может быть артефактов, которые появились в MS по мере естественного роста. То, что deprecated в MS DAX, не будет реализовано в Visiology. Именно поэтому в Visiology нет поддержки SUMMARIZE
с параметрами expression. Вместо этого для расчета выражений используется SUMMARIZECOLUMNS
. Это и быстрее, и удобнее. А трансформировать уже имеющиеся выражения с SUMMARIZE
помогает чат-бот ViTalk GPT.
Повышая производительность
Но я вам обещал как негативные, так и позитивные примеры работы с этим оператором. Конечно, существуют разные кейсы, когда SUMMARIZE
полезен. Например, давайте посмотрим, в каких случаях SUMMARIZE
позволяет повысить производительность. Ведь этот оператор может быть эффективным, когда используется для группировки без выражения.
В качестве примера рассмотрим таблицу продаж sales
с полями номер заказа ordernumber
, ключ даты datekey
в формате YYYYMMDD
, ID магазина storekey
и сумма заказа amount
, таблицу магазинов store
с полями ID магазина storekey
, имя магазина storename
и календарь calendar с полями ключ даты datekey
в формате YYYYMMDD
, месяц month
и сама дата в формате даты date
, т.е. схему данных:
Допустим, нам нужно посчитать значение флага «активный магазин» 0
или 1
для каждого магазина, причем мы присваиваем 1
, если сумма продаж по магазину SUM(sales[orderamount])
превышает заданную сумму за промежуток времени (например, 5000000000
), будем считать также, что промежуток времени ограничивается фильтрами дашборда, и его не нужно явно учитывать в DAX запросе. Кроме того, нам нужно посчитать и количество таких магазинов, что вполне естественно.
Понятно, что задача решается созданием новой меры. Также в задаче удобен флаг со значениями 0
и 1
, т.к. дает возможность суммирования и определения общего количества таких магазинов.
Мера на основе IF для расчетов с группировкой по магазинам
Меру можно создать при помощи IF
на основе условия на сумму продаж SUM(sales[orderamount])
, однако для корректной работы такую меру всегда нужно использовать вместе с группировкой по магазинам, т.е. с sales[storekey]
:
Is Store Actual =
IF ( SUM ( sales[orderamount] ) > 5000000000, 1, 0 )
Эту меру можно добавить, например, в таблицу store
и назвать store[Is Store Actual]
.
Дашборд для такой меры выглядит следующим образом:
Visiology генерирует следующий DAX для такой таблицы:
DEFINE
VAR __DS0Core =
SUMMARIZECOLUMNS (
'store'[storename],
“SUM(orderamount)”, SUM('store'[orderamount]),
"Is Store Actual", 'store'[Is Store Actual]
)
EVALUATE
TOPN (
3000,
__DS0Core,
'store'[storename], ASC,
[Is Store Actual], ASC
)
ORDER BY
'store'[storename] ASC,
[Is Store Actual] ASC
У такой меры store[Is Store Actual]
есть недостаток — она верно работает только в некотором контексте, а именно, при наличии группировки по sales[storekey]
, или, например, при группировке по уникальным именам магазинов — по store[storename]
. Поэтому с данной мерой нет возможности подсчитать суммарное количество активных магазинов в виде одного числа, как скалярный результат.
Мера с SUMMARIZE
Хорошо бы сделать эту меру несколько более универсальной, «зашить в неё» группировку по магазину, чтобы использовать меру в разных контекстах…ну или хотя бы посчитать количество таких магазинов.
Когда нужно получить агрегированные данные таблицы с группировкой по столбцам (условно, с GROUP BY
), как и в случае нашей меры (группировка по ID магазина sales[storekey]
с суммированием sales[amount]
), помогает SUMMARIZE
со столбцами группировки, как один из аргументов SUMX
:
Actual Stores SUMMARIZE = SUMX (
SUMMARIZE ( sales, sales[storekey] ),
CALCULATE (IF ( SUM ( sales[orderamount] ) > 5000000000, 1, 0 ) )
)
Важно, что CALCULATE
в этой мере используется, чтобы эта мера верно работала в таблице без группировки по sales[storekey]
, т.е. для подсчета общего количества активных магазинов.
Соответственно, когда мера используется в таблице с группировкой по sales[storekey]
, SUMX
суммирует всего лишь «одно слагаемое» для каждого магазина sales[storekey]
. При наличии группировки можно обойтись даже и без SUMX
, но мера будет не такой универсальной и не сможет использоваться с любым контекстом — она будет давать верные результаты только в таблице с группировкой, например, по sales[storekey]
.
SUMX
он на верхнем уровне хоть и служит лишь для сложения результатов по разным магазинам (когда не задана группировка по sales[storekey]
), зато повышает универсальность меры.
Кстати, Visiology генерирует DAX для этой таблицы, совпадающий с DAX для предыдущего дашборда с точностью до имени меры.
Для 50 млн записей DAX запрос выполняется примерно за 1-2 секунды.
При использовании этой меры в каком-то смысле «производительность гарантирована» за счет группировки в SUMMARIZE
по ID магазина. То есть благодаря SUMMARIZE
в плане выполнения запроса вместо общего количества записей в таблице фактов sales
появляются только записи sales
, сгруппированные по sales[storekey]
, и количество сгруппированных записей всегда будет на порядок или даже на порядки ниже, чем исходное количество записей в таблице sales
. Мера с SUMMARIZE
может применяться и с группировкой по ID магазина, и самостоятельно (для подсчета суммы по всем магазинам), как видно на следующем дашборде.
Visiology генерирует следующий DAX запрос:
DEFINE
VAR __DS0Core =
SUMMARIZECOLUMNS ( "Actual Stores SUMMARIZE", 'sales'[Actual Stores SUMMARIZE] )
EVALUATE
TOPN ( 3000, __DS0Core, [Actual Stores SUMMARIZE], ASC )
ORDER BY [Actual Stores SUMMARIZE] ASC
Пример сравнения SUMMARIZE с другой мерой — одинаковые результаты, но разная производительность
В итоге можно сказать, что SUMMARIZE
позволяет создавать эффективные меры с точки зрения производительности, причем их можно использовать в разных контекстах, а именно, как с полями группировки (например, store[storename]
), так и без полей группировки. Возникает вопрос, с чем сравнить меру с SUMMARIZE
, чтобы понять, так ли он нужен? Могут быть разные варианты, можно рассмотреть один из них, менее оптимальный, по сравнению с SUMMARIZE
, на таблице с группировкой по sales[storekey]
:
Actual Stores DISTINCTCOUNT = CALCULATE (
DISTINCTCOUNT(sales[storekey]),
FILTER(sales, SUM(sales[orderamount]) > 5000000000)
)
При наличии в таблице группировки по sales[storekey]
CALCULATE
всегда рассчитывается на продажах одного магазина sales[storekey]
, поэтому DISTINCTCOUNT(sales[storekey])
возвращает 1
при выполнении условия по сумме продаж больше 5000000000
, и 0
иначе, что и требуется. Хочется отметить, что мера с DISTINCTCOUNT
взята для сравнения лишь в качестве примера, не как best practice в именно для данного случая.
В результате мы получим дашборд следующего типа с мерой sales[Actual Stores DISTINCTCOUNT]
. Запрос на 50 млн записей выполняется за несколько секунд. Это значительно больше пары секунд и дольше, чем генерация дашборда с SUMMARIZE
:
DAX запрос, который генерирует Visiology для этой таблицы, совпадает с другими с точностью до имени меры, а результаты с группировкой по sales[storekey]
совпадают с результатами меры sales[Actual Stores Summarize]
.
Видно, что здесь используется DISTINCTCOUNT(sales[storekey])
, и эта операция и весь план выполнения запроса работают медленнее по сравнению с SUMMARIZE
и его фактической GROUP-BY
-логике для sales[storekey]
. То есть с точки зрения производительности для получения сгруппированной таблицы полезно использовать именно SUMMARIZE
.
Заключение
Таким образом, разумное применение SUMMARIZE
(без expression) позволяет строить производительные меры, которые можно более свободно использовать в разных контекстах, как с группировкой по столбцу (в нашем случае ID магазина), так и самостоятельно, без такого столбца.
Надеюсь, описанные приемы работы с SUMMARIZE
могут быть полезны для дашбордов. Успехов в Business Intelligence :)