Power BI: DAX Query View vs DAX Studio

До скоро DAX Studio беше незаменим помощник в проучването на данни, разработването на метрики и тест за производителност на метриките. От края на 2023 година, с обновлението на Power BI, Microsoft включиха в новата версия инструмента DAX Query View, който има потенциала да измести DAX Studio.

До каква степен DAX Query View може да замести DAX Studio?

Да разгледаме какво ни дава всеки един от тези инструменти и да оценим може ли DAX Query View да замести DAX Studio пълноценно.

Специфични за DAX Query View възможности

  1. По-удобни клавишни комбинации

DAX Query View притежава удобни бързи клавиши за преместване и дублиране на редове и редица други, които внасят динамика и удобство при писане DAX заявки.

Кл.комбинацияДействие
Alt + Up/Downмести ред нагоре/надолу
Ctrl + Dмаркира всички срещания на избран текст
Shift + Alt + Up/Downкопира ред нагоре/надолу
Ctrl + /коментира/маха коментар от редовете
F5изпълнение на заявката
  1. Доста добро дописване на променливи и метрики (Intellisense)
  2. Добавяне или промяна на метрики „с един клик“

При дефиниране на метрика, над нея се появява линк „Update model: Add new measure“, а при редактиране на съществуваща, линкът е „Update measure“ и метриката се добавя или обновява в посочената таблица.

За сравнение, при DAX Studio, метриките трябва да се пренасят една по една с копиране на изразите и прехвърлянето им в Power BI е тромава процедура.

Какво липсва в DAX Query View?

Най-големият недостатък на DAX Query View е липсата на каквито и да е било настройки, но не трябва да забравяме, че този инструмент все още не е част от стабилната версия на Power BI и е необходимо да се активира от Preview Features, за да се показва в интерфейса на програмата.

Специфични възможности на DAX Studio

  1. View Metrics

Това е доста полезна функция, която показва за всяка колона как е компресирана, колко място заема и др. и помага да открием резерви за оптимизация на модела на данните.

  1. Run Benchmark, Server Timings и Query Plan

Три много полезни инструмента, с които можем да сравняваме различни варианти на метрики и да изберем оптималния, да изследваме логическия и физическия план за изпълнение на заявките, да видим статистики за Storage Engine и Formula Engine на ядрото на Power BI.

  1. Резултати от заявка

В DAX Studio има възможност резултатите от заявка, освен да се видят на екрана, и да се експортират в различни формати: csv, MS Excel или директно копиране в Clipboard и след това да се пренесат в друга програма.

Практическа задача

Възможностите, предимствата и недостатъците на DAX Query View и DAX Studio могат най-добре да се видят с помощта на практическа задача.

Задача

Необходимо е да се разработи метрика, с която да се сравни средната стойност на поръчка по години (2014 – 2017), региони (West, East, Central, South) и сегмент на клиента (Consumer, Corporate, Home Office) и да се представят данните визуално.

Модел на данните

Схема на модела в Power Bi

Кратко описание на таблиците от модела:

1) fact-sales Факт таблица с продажбите
2) dim-customers – данни за клиентите (тук се намира колоната със сегменти)
3) dim-territories – географски данни (тук се намира колоната с региони)
4) dim-shipping-class – данни за доставка
5) dim-products – данни за продуктите
6) Calendar – календарна таблица
7) Отделна таблица с метрики – SalesM

Обобщени данни за продажбите

Общата стойност на продажбите е 11.49$ милиона, a общият брой продадени единици е 38 000.

Особеност на метриката, която трябва да се състави, е в това, че детайлността на факт таблицата е „продажба на продукт“, а ние трябва да получим резултат, отнасящ се до поръчките т.е. изчисленията трябва да са с данни с по-малка детайлност от тази в таблицата.

Това ясно се вижда от следващия фрагмент от факт таблицата. Например поръчка 100013 се среща 3 пъти, защото в нея има 3 продукта и когато изчисляваме средната стойност, трябва да използваме общата стойност на поръчката, а не на индивидуалните продажба.

ORDER_IDUNIT_PRICEQUANTITYDISCOUNTSHIPPING_DAYSPRICEPRICE_DISCOUNTCUSTOMER_IDPRODUCT_IDTERRITORY_IDSIPPING_CLASS_IDORDER_DATE_KEYSHIP_DATE_KEY
1000135.6820511.3611.3659105212017110620171111
1000138.731058.738.7359104212017110620171111
10001315.2820530.5630.5659103212017110620171111

За по-лесно проследяване на действията и логиката, ще разделим решението на малки стъпки.

Стъпка 1: Основна заявка

Избираме два фактора – година и сегмент на клиента и изчисляваме общата стойност на продажбите.

EVALUATE
    SUMMARIZECOLUMNS(
        -- group by --
        'Calendar'[Year],
        'dim-customers'[SEGMENT],
        -- expr --
        "@Sales", [Sum of Sales]
    )
ORDER BY
    [Year]
,   [@Sales] DESC

Резултат:

Calendar[Year]dim-customers[SEGMENT][@Sales]
2014Consumer1358335.3534
2014Corporate647032.5567
2014Home Office462915.8246
2015Consumer1355935.9435
2015Corporate648794.7781
2015Home Office380170.658
2016Consumer1470278.3396
2016Corporate1054352.1396
2016Home Office525426.877
2017Consumer1634797.7643
2017Corporate1165928.1982
2017Home Office784093.6389

Стъпка 2: Израз, с който ще изчисляваме средната стойност

Изяснихме по-горе, че е необходима различна детайлност на данните т.е. резултатът трябва да е за поръчка, а не продаден продукт и като повечето неща в DAX, можем да избираме между няколко възможни решения.

Как да променим детайлността на данните без да създаваме допълнителни таблици в модела?

Вариант 1: с функцията SUMMARIZE() групираме по колона ORDER_ID.

VAR orders =
    SUMMARIZE(
        -- table --
        'fact-sales',
        -- group by --
        'fact-sales'[ORDER_ID]
    )

Вариант 2: с функцията DISTINCT() извличаме неповтарящите се стойности в ORDER_ID.

VAR orders_ds =
    DISTINCT('fact-sales'[ORDER_ID])

Вторият вариант изглежда по-прост, но от това не следва автоматично, че е по-добър. По-късно ще изследваме разликите в производителността на двата варианта и ще определим оптималния.

Независимо от това кой от двата варианта ще използваме, средната стойност ще изчислим с функцията AVERAGEX().

Всички функции в DAX, които имат суфикс X се наричат функции итератори и те получават два параметъра: таблица и израз.

EVALUATE
    SUMMARIZECOLUMNS(
    -- group by --
    'Calendar'[Year],
    'dim-customers'[SEGMENT],
    -- expr --
    "@Sales", [Sum of Sales],
    "@Avg Order Amt",
        VAR orders =
            SUMMARIZE(
                -- table --
                'fact-sales',
                -- group by --
                'fact-sales'[ORDER_ID]
            )
        VAR orders_ds =
            DISTINCT('fact-sales'[ORDER_ID])
        RETURN
            AVERAGEX(orders, [Sum of Sales] )
    )
ORDER BY
    [Year]
,   [@Sales] DESC

Резултат:

Calendar[Year]dim-customers[SEGMENT][@Sales][@Avg Order Amt]
2014Consumer1358335.35342592.243
2014Corporate647032.55672254.4688
2014Home Office462915.82462929.847
2015Consumer1355935.94352534.4597
2015Corporate648794.77812079.4704
2015Home Office380170.6581990.4223
2016Consumer1470278.33962258.4921
2016Corporate1054352.13962498.4648
2016Home Office525426.8772171.1854
2017Consumer1634797.76431866.2075
2017Corporate1165928.19822364.9659
2017Home Office784093.63892465.7033

В резултата получаваме стойността на поръчките за всеки сегмент по години и средната стойност на поръчка.

Стъпка 3: Дефиниране на метрики

Изразът за изчисляване на средната стойност можем да го дефинираме като метрика. В таблицата SalesM ще бъдат пренесени всички наши метрики с Add to model.

В процеса на решаване на задачата, се появява и още една идея за изчисляване на средната стойност:

VAR sales_amt = [Sum of Sales]
VAR n_orders = DISTINCTCOUNT( 'fact-sales'[ORDER_ID])
VAR result = DIVIDE( sales_amt, n_orders)
RETURN
    result

В този случай, просто разделяме стойността на поръчките на техния брой като допълнително можем променливата n_orders да дефинираме като метрика.

Така получаваме три варианта, от които трябва да изберем оптималния. Разликата между последния вариант и другите два е в това, че AVERAGEX() решението е по-универсално.

Например, ако се окаже, че за средна оценка е по-подходящо да изберем медианата, то можем да заменим AVERAGEX() с MEDIANX(), но не можем да получим медианата, като разделим стойността на поръчките на техния брой.

DEFINE
    MEASURE SalesM[Avg Order Amt A] =
        VAR orders =
            SUMMARIZE(
                -- table --
                'fact-sales',
                -- group by --
                'fact-sales'[ORDER_ID]
            )
        VAR orders_ds =
            DISTINCT('fact-sales'[ORDER_ID])
        RETURN
            AVERAGEX(orders, [Sum of Sales])
    // -- N Ordes --
    MEASURE SalesM[N orders] =
        DISTINCTCOUNT( 'fact-sales'[ORDER_ID])
    // -- Avg Order Amt B --
    MEASURE SalesM[Avg Order Amt B] =
            VAR sales_amt = [Sum of Sales]
            VAR n_orders = [N orders]
            VAR result = DIVIDE( sales_amt, n_orders)
        RETURN
            result
EVALUATE
    SUMMARIZECOLUMNS(
        -- group by --
        'Calendar'[Year],
        'dim-customers'[SEGMENT],
        -- expr --
        "@Sales", [Sum of Sales],
        "@Avg Order Amt A", [Avg Order Amt A],
        "@Avg Order Amt B", [Avg Order Amt B]
    )
ORDER BY
    [Year]
,   [@Sales] DESC

Резултат:

Calendar[Year]dim-customers[SEGMENT][@Sales][@Avg Order Amt A][@Avg Order Amt B]
2014Consumer1358335.35342592.2432592.243
2014Corporate647032.55672254.46882254.4688
2014Home Office462915.82462929.8472929.847
2015Consumer1355935.94352534.45972534.4597
2015Corporate648794.77812079.47042079.4704
2015Home Office380170.6581990.42231990.4223
2016Consumer1470278.33962258.49212258.4921
2016Corporate1054352.13962498.46482498.4648
2016Home Office525426.8772171.18542171.1854
2017Consumer1634797.76431866.20751866.2075
2017Corporate1165928.19822364.96592364.9659
2017Home Office784093.63892465.70332465.7033

Стъпка 4: Кой вариант е по-добър?

За избора на оптимален вариант трябва да използваме DAX Studio, тъй като в DAX Query View няма подобна функционалност.

Първият инструмент, с който можем да тестваме заявката и метриките, е Server Timings, като при използването му не трябва да забравяме да включим бутона Clear on Run, за да се изчиства кеша на заявките преди всеки тест.

Server Timings резултат в DAX Studio

  • Storage Engine (SE) – отговаря за достъпа до данните и част от изчисленията. Може да работи паралелно.
  • Formula Engine (FE) – извършва онези изчисления, които не се поддържат от SE (например, ако с ROUND() закръглим резултатите).

Получаваме за FE и за SE по 9 ms време за изпълнение на заявката като SE изпълнява паралелно три заявки.

При работа със Server Timings е добре да изпълним заявката няколко пъти, тъй като върху времето за изпълнение оказва влияние общото натоварване на системата и задачите, които изпълнява операционната система.

Удобен инструмент представлява и Run Benchmark, с който могат да се пуснат многократни тестове при „студен“ и „топъл“ кеш, както и обобщени и детайлни статистики за изпълнението на заявката.

Benchmark в DAX Studio

За нашия тест ще използваме по-тежкият режим – „студен“ кеш, и 10 изпълнения на заявката.

Вариант 1: SUMMARIZE()

CacheStatisticTotalDurationSE Duration
ColdAverage139.145.8
ColdStdDev73.893993140323841.8457484897411
ColdMin373
ColdMax224136

Вариант 2: DISTINCT()

CacheStatisticTotalDurationSE Duration
ColdAverage115.843.2
ColdStdDev90.649263035553143.2352736650167
ColdMin334
ColdMax357155

DISTINCT() дава малко по-добра производителност, като средните времена са с по-ниски стойности, минималните времена почти съвпадат, а максималните при SUMMARIZE() са по-ниски. но като окончателен вариант оставяме в метриката DISTINCT().

Ако решите да проведете тестовете самостоятелно, то може да получите по-различни стойности от дадените в таблицата.

Всички изводи по отношение на функциите DISTINCT() и SUMMARIZE(), както и взетите решения, се отнасят за конкретния модел и данни т.е. не може да твърдим, че DISTINCT() по принцип е по-бърза.

SUMMARIZE() e по-универсална функция от DISTINCT(). Например, ако трябва да групираме по няколко колони, няма да може да използваме DISTINCT().

Вариант с DIVIDE()

CacheStatisticTotalDurationSE Duration
ColdAverage71.229.1
ColdStdDev47.353986104656532.2712462314881
ColdMin103
ColdMax172101

Като резултат получаваме стойности, които са по-ниски от тези при DISTINCT() и окончателно се спираме на метриката за изчисляване на средната стойност:

DEFINE
    MEASURE SalesM[Avg Order Amt B] =
            VAR sales_amt = [Sum of Sales]
            VAR n_orders = [N orders]
            VAR result = DIVIDE( sales_amt, n_orders)
        RETURN
            result

В Power BI има инструмент, наречен performance analyzer, но с него се изчислява производителността на визуален компонент и освен това, той не дава подробна статистика за изпълнението.

Визуално представяне на данните

За визуализация на данните избираме стълбова диаграма и добавяме необходимите колони и метрика.

Bar charts

Заключение

Като заключение може да се каже, че DAX Query View като цяло е удобен инструмент за съставяне на DAX заявки и разработка на метрики. Разбира се има още какво да се подобрява в него, но с времето се надявам да става все по-добър.

DAX Studio за момента компенсира важна част от липсващата функционалност. В момента, в който тя се появи (ако се появи), DAX Studio може да служи като алтернативно средство за работа с DAX и не трябва да забравяме, че DAX Studio също не е спрял развитието си.

Използваните данни и решението на задачата можете да изтеглите от тук.

Искате да научите повече за Power BI?

Включете се в курса Power BI: Анализ на данни и изготвяне на отчети.

Научете повече

Автор: Devise Expert