Rozdział 1
Wprowadzenie do nauki języka DAX
DAX (akronim ten oznacza Data Analysis eXpressions, czyli wyrażenia analizy danych) jest językiem programowania wykorzystywanym przez narzędzia Microsoft Power BI, Microsoft SQL Server Analysis Services (SSAS) oraz Microsoft Power Pivot for Excel. Został utworzony w roku 2010, wraz z pierwszym wydaniem narzędzia PowerPivot for Excel 2010. Tak, w roku 2010 PowerPivot pisany był bez odstępu; spacja w nazwie Power Pivot pojawiła się dopiero w roku 2013. Z upływem czasu DAX zdobył popularność zarówno wśród zaawansowanych użytkowników Excela, którzy używają go do tworzenia modeli danych Power Pivot w skoroszytach Excela, jak i w środowisku Business Intelligence (BI), gdzie DAX służy do budowania modeli dla Power BI SSAS. DAX jest obecny w wielu różnych narzędziach. Wszystkie one jednak wykorzystują ten sam wewnętrzny silnik określany mianem Tabular, czyli tabelaryczną bazę danych. Z tych powodów często będziemy używać pojęcia modele tabelaryczne, który to termin obejmuje wszystkie te różne narzędzia.
W roku 2015 firma Microsoft wydała pierwszą wersję Power BI. Program ten szybko zyskał ogromną popularność w społeczności analityków danych i stał się głównym narzędziem raportowania w większości firm na całym świecie. Tym samym gwałtownie wzrosła liczba użytkowników i programistów uczących się języka DAX. Według stanu z roku 2023 Power BI jest centralnym komponentem platformy Microsoft Fabric, gdzie DAX odgrywa kluczową rolę w konstruowaniu modeli semantycznych. Śmiało można powiedzieć, że głównym powodem, dla którego ludzie w ogóle uczą się języka DAX, jest budowanie modeli semantycznych dla raportów Power BI. Dlatego w tej książce przyjęliśmy założenie, że nasi czytelnicy znają program Power BI, a także będziemy omawiać pewne techniki programistyczne, które są najbardziej użyteczne w modelach semantycznych dla Power BI.
DAX jest językiem prostym. Powiedziawszy to, trzeba zauważyć, że DAX różni się od większości innych języków programowania i przyzwyczajenie się do niego może wymagać trochę czasu. Z naszych doświadczeń, zdobytych przy nauczaniu tego języka tysięcy ludzi, opanowanie podstaw DAX jest proste i naturalne: można zacząć go używać już po paru godzinach. Jednak gdy przyjdzie potrzeba zrozumienia zaawansowanych koncepcji, takich jak konteksty wykonania, iteracje i propagowanie kontekstów, wszystko razem może wydać się bardzo złożone. Nie należy się poddawać! Trochę cierpliwości. Gdy nasz umysł zacznie już ogarniać te pojęcia, odkryjemy, że DAX istotnie jest łatwym językiem. Potrzeba tylko nieco czasu, aby do niego przywyknąć.
Większość naszych studentów, a zapewne również większość czytelników tej książki, ma już pewne doświadczenie w pisaniu kodu DAX. Niektórzy tworzyli raporty od paru miesięcy. Inni dysponują doświadczeniem obejmującym wiele lat. Część z nich bywa (niestety) przekonana, że - dzięki doświadczeniu - wiedzą wystarczająco dużo o DAX, aby pominąć części wstępne i przejść od razu do bardziej zaawansowanej tematyki. Jako autorzy, nie polecamy pominięcia żadnej części. Na naszych wykładach i w prywatnych szkoleniach zauważyliśmy, że wiele osób potrzebuje pewnego oduczenia się, aby móc zacząć na nowo od czystych, solidnych podstaw.
Niezależnie od posiadanego już doświadczenia z językiem DAX i Power BI zdecydowanie radzimy, aby zacząć od samego początku i wolno zmierzać w stronę trudniejszych, zawansowanych tematów. Chodzenie na skróty rzadko się opłaca. Włożyliśmy sporo wysiłku w opracowanie ścieżki nauki, którą proponujemy w tej książce. Podążaj zatem tą ścieżką i zwalniaj, gdy coś nie zostanie dostatecznie dobrze zrozumiane. Gdy natrafisz na coś trudniejszego, być może trzeba będzie przeczytać jakiś rozdział lub podpunkt wielokrotnie, aż stanie się krystalicznie czysty.
Ten początkowy rozdział zaczyna się od wyjaśnienia, czym jest model semantyczny i znalezienia odpowiedzi na bardzo ważne pytanie: Dlaczego DAX w ogóle istnieje? Omówimy również to, jak bardzo istotny dla naszego kodu DAX jest nasz model danych, a także porównamy DAX z innymi językami programowania, zwracając uwagę na pewne unikatowe cechy DAX. Jest to również jedyny rozdział, w którym używamy bardziej kolokwialnego i bezpośredniego języka - bardziej "nieformalny", jeśli ktoś woli. W dalszej części nie będzie zbyt wiele miejsca na żarty i poluzowanie. Ciesz się tą chwilą wolności i przygotuj na ciężką pracę nad resztą książki.
Dlaczego DAX w ogóle istnieje?
Pytanie postawione w tytule tego podrozdziału wcale nie jest głupie. Kiedy DAX został przedstawiony po raz pierwszy w roku 2010, od dawna dostępnych było wiele języków programowania i zapytań. Tak więc zupełnie naturalne było pytanie "Czy warto było tworzyć jeszcze jeden język, taki jak DAX?" Odpowiedzią jest mocne TAK, choć wymaga to odpowiednich wyjaśnień.
Zacznijmy od początku: potrzebujemy wprowadzić pojęcie modelu semantycznego. A mówiąc ściślej, musimy wyjaśnić, czym jest model semantyczny w świecie Power BI, gdyż model semantyczny Power BI różni się od ogólnej koncepcji modelu w teorii baz danych. Nie chcemy tu przedstawiać żadnej formalnej definicji. Mówiąc w skrócie, w teorii baz danych model semantyczny to opis wysokiego poziomu encji biznesowych, wraz z relacjami pomiędzy tymi encjami. Istnieje rozróżnienie pomiędzy modelem danych a modelem semantycznym: model danych opisuje fizyczne tabele bazy danych i relacje pomiędzy nimi, podczas gdy model semantyczny opisuje encje (byty) logiczne i ich powiązania. Encja, taka jak klient, może być przechowywana w wielu, może nawet dziesięciu różnych tabelach, przedstawianych jako dziesięć tabel w modelu danych, ale z punktu widzenia modelu semantycznego jest to pojedynczy byt. W teorii baz danych model danych i model semantyczny są zatem różnymi koncepcjami. Natomiast w świecie analityki biznesowej (business intelligence), a zwłaszcza w Power BI, wszystko jest nieco prostsze.
Modele danych, których używamy w systemach analitycznych, są stosunkowo proste i zazwyczaj jedna encja jest przechowywana w pojedynczej tabeli. Jednak w świecie analityki biznesowej możemy sformułować równość encja = tabela. Można więc śmiało powiedzieć, że w tej dziedzinie model semantyczny jest tożsamy z modelem danych. Jednak na początku roku 2020 podjęto ważną decyzję, aby w Power BI przypisać terminowi "model semantyczny" szczególne znaczenie.
W Power BI model semantyczny to model danych wzbogacony o miary i obliczenia, które pozwalają użytkownikom wyciągać wnioski z danych bez konieczności pisania bardzo złożonych zapytań. Pomocny tu będzie przykład. Wyobraźmy sobie, że ładujemy trzy tabele do Power BI: Sales, Customer oraz Date. Następnie budujemy standardowy zbiór relacji, aby uzyskać model danych przedstawiony na rysunku 1-1.
Rysunek 1-1 Trzy proste tabele załadowane do Power BI tworzą model danych.
Tym, co właśnie stworzyliśmy, jest model danych, ale jeszcze nie model semantyczny. Mamy tu trzy encje (tabele) i ich relacje. Brakuje jednego elementu, aby uczynić go modelem semantycznym: miar.
Obecność miar jest tym, co przekształca model danych w model semantyczny. Choć z pewnością możliwe jest zbudowanie macierzy opartej na tym prostym modelu i wyświetlenie np. ilości sprzedaży według lat lub marek, każde nieco bardziej zaawansowane obliczenie będzie wymagało pisania złożonych zapytań, co nie jest opcją dostępną dla typowego użytkownika. Nawet pozornie proste obliczenia - takie jak wielkość sprzedaży (co wymaga pomnożenia ilości przez cenę netto) albo marża (co wymaga odjęcia całkowitego kosztu od ceny netto) - są poza zasięgiem.
Co więcej, użytkownicy oczekują od swoich modeli Power BI większej funkcjonalności, na przykład obliczania wzrostu rok do roku, ustalania rankingu klientów w celu zidentyfikowania tych najlepszych, analiz Pareto czy utrzymywania klientów. Istnieje wiele ciekawych obliczeń, które deweloper może utworzyć, aby przekształcić model danych (zawierający same dane) w model semantyczny (który zapewnia wgląd w te dane).
Tak więc otrzymujemy proste, ale kluczowe równanie:
Model semantyczny = Model danych + Miary
Miary są sercem Power BI. Stąd główny wymóg dla języka wykorzystywanego w Power BI jest taki, że musi być to język, którego najważniejszym celem jest definiowanie miar. Nie będzie zatem zaskoczeniem, że DAX jest językiem nakierowanym na miary, w którym większość (jeśli nie wszystkie) obliczenia odbywają się poprzez miary.
Przed pojawieniem się DAX istniał już inny język, który spełniał wiele spośród tych wymagań: MDX. Jednak MDX narzuca wiele ograniczeń na model, który musi być ściśle wielowymiarowy i nie obsługuje obliczeń na poziomie wiersza ani nie radzi sobie dobrze ze złożonymi relacjami. Tym samym potrzebny był bardziej elastyczny język.
Model semantyczny zawiera również wiele innych metadanych, opisujących, jak prezentować dane hierarchiczne, jak sortować kolumny i tak dalej, aby wymienić tylko kilka. Kod DAX jest wykorzystywany także w innych obszarach modelu semantycznego, w tym w rolach zabezpieczeń, grupach obliczeń, kolumnach obliczanych i tak dalej.
Jednak to wszystko nie daje odpowiedzi na główne pytanie: Czy DAX jest naprawdę potrzebny jako nowy język dla modeli semantycznych? Ponownie odpowiedź brzmi tak, gdyż żaden z języków dostępnych w tamtym czasie (poza MDX) nie dysponował żadnymi funkcjonalnościami do definiowania miar.
Stąd naturalnie wynika kolejne pytanie: Dlaczego spośród ogromnego bogactwa języków żaden nie pasował dobrze do definiowania miar? Przyczyną jest to, że miary mają pewne unikatowe właściwości, którymi nigdy nie zajmował się żaden język programowania. Miara jest czymś, czego mają używać użytkownicy tworzący raporty, o niewielkich umiejętnościach technicznych (programistycznych), czego mogliby użyć w dowolnym raporcie, a dodatkowo musi działać bez zakłóceń. Miara ma obliczyć pożądaną wartość bez względu na to, jak skomponujemy raport. Ten prosty fakt sprawia, że miary są czymś unikatowym.
Rozważmy prosty przykład. Przyjrzyjmy się raportowi pokazanemu na rysunku 1-2, pokazującym wielkość sprzedaży i jej wzrost w porównaniu do poprzedniego roku, podzielone według kontynentów i krajów, ograniczonemu do tylko dwóch lat.
Rysunek 1-2 Prosty raport pokazujący sprzedaż i jej wzrost według lat i geografii.
Pomimo tego, że jest to bardzo prosty raport, pokazuje dobitnie potęgę miar. Same miary używane w tym raporcie są dość proste:
Miary w tabeli Sales
Sales Amount = SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
Growth =
VAR SalesCY =
[Sales Amount]
VAR SalesPY =
CALCULATE ( [Sales Amount], SAMEPERIODLASTYEAR( 'Date'[Date] ) )
VAR Result =
DIVIDE ( SalesCY - SalesPY, SalesPY )
RETURN
Result
Miara Growth najpierw oblicza wielkość sprzedaży dla bieżącego i poprzedniego roku, po czym używa tych wartości do obliczenia wzrostu procentowego. Zwróćmy uwagę, że w żadnym miejscu tej miary deweloper nie musi się zajmować wyborami, których użytkownik mógłby dokonać w raporcie. Na rysunku 1-2 użytkownik zdecydował się na pofragmentowanie danych według geografii (co wymaga zgrupowania danych według Continent i Country), wybrał dwa lata (co wymaga zastosowania filtra), a na koniec umieścił pole Year w obszarze kolumn (co wymaga dalszego pogrupowania według lat). Niemniej jednak miara ta będzie działać równie dobrze w każdym innym scenariuszu.
Operacje filtrowania i grupowania wymagane przez raport są wstrzykiwane do miary, która zachowuje się poprawnie, obliczając swoje wyniki dla podzbiorów danych. Ten drobny szczegół jest tak istotny! Zachowanie miary zależy od raportu. Albo ujmując to inaczej, raport zmienia zachowanie miary.
Choć wygląda to tak naturalnie dla użytkownika Power BI, jest to cecha, jakiej nie posiada żaden inny język: możliwość pisania kodu, który może być dostosowany do konkretnych potrzeb raportu bez konieczności zajmowania się z góry wszelkimi możliwościami. W zwykłych językach programowania zawsze istniała możliwości pisania funkcji, które zmieniają swoje zachowanie zależnie od dostarczonych argumentów. Jednak programista musiał to przemyśleć zawczasu. W języku DAX filtry z raportu przepływają do wnętrza miary poprzez kontekst filtru, w tak naturalny sposób, że większość deweloperów nawet nie jest świadomych złożoności takiego zachowania.
Trzeba pamiętać, że ten przykład jest skrajnie prosty. W tym rozdziale nie będziemy się jeszcze zagłębiać w prawdziwą złożoność języka DAX. Im bardziej skomplikowany jest kod DAX (albo struktura raportu), tym bardziej wzrasta złożoność.
Choć bez wątpienia dałoby się uzyskać ten sam wynik poprzez zapytanie SQL, istotą sprawy jest to, że prostota, z jaką DAX pozwala nam napisać formuły, które można wykorzystywać w dowolnym raporcie, jest niemal unikatowa. Wszystkie składniki języka DAX zostały starannie zaprojektowane wokół koncepcji, że DAX to język przeznaczony do tworzenia miar, co z kolei pozwala na łatwe wykorzystania kodu w dowolnym raporcie.
Jak model danych wpływa na nasz kod DAX
Jako że kod DAX działa w modelu danych, kluczowe jest pamiętanie, że różne właściwości tego modelu wpływają na jego wykonywanie. Kod, który działa gładko na pewnym modelu, może przestać działać albo tworzyć nieoczekiwane wyniki, jeśli model się zmieni.
Jako że będziemy w tej książce stale omawiać tabele, relacje i ich rozmaite aspekty, warto będzie tu przedstawić terminologię, której będziemy później używać.
Model danych zawiera tabele. Wszyscy wiemy, czym jest tabela: jest to zbiór wierszy zawierających dane, przy czym każdy wiersz podzielony jest na kolumny. Każda kolumna ma określony typ danych i zawiera pojedynczy element informacji. Jeśli model danych zawiera wiele tabel, zapewne są one połączone relacjami. Relacja to związek pomiędzy dwiema tabelami. Gdy dwie tabele są połączone jedną lub kilkoma relacjami, mówimy, że są powiązane (są w relacji). Graficznie relacja jest przedstawiana jako linia łącząca dwie tabele. Rysunek 1-3 przedstawia przykład modelu danych.
Oto kilka kluczowych aspektów relacji:
Dwie tabele w relacji nie pełnią takich samych ról. Są one określane jako strona jednowartościowa oraz strona wielowartościowa relacji. Zwróćmy uwagę na relację pomiędzy tabelami Product i Product Subcategory na rysunku 1-3. Pojedyncza podkategoria (subcategory) zawiera wiele produktów, podczas gdy każdy produkt należy tylko do jednej podkategorii. Tym samym tabela Product Subcategory jest stroną jednowartościową (jedna podkategoria) relacji, podczas gdy tabela Product jest stroną wielowartościową (zawierającą wiele produktów z wybranej podkategorii).
Rysunek 1-3 Ten model danych składa się z sześciu tabel z aktywnymi (linie ciągłe) i nieaktywnymi (linie kropkowane) relacjami.
Dwa szczególne rodzaje relacji to relacje 1:1 oraz relacje słabe. W relacji 1:1 obie strony (tabele) są jednowartościowe, podczas gdy w relacji słabej obie tabele mogą być stroną wielowartościową. Takie rodzaje relacji nie są zbyt pospolite.
Kolumny użyte do utworzenia relacji (które zwykle noszą tę samą nazwę w obu tabelach) są nazywane kluczami relacji. Po stronie jednowartościowej relacji kolumna ta musi zawierać unikatowe wartości w każdym wierszu. Po stronie wielowartościowej ta sama wartość może być (i zazwyczaj jest) powtórzona w wielu różnych wierszach. Gdy kolumna zawiera unikatową wartość dla każdego wiersza, nazywana jest kluczem (lub kolumną klucza) tabeli.
Relacje mogą tworzyć łańcuch. Każdy produkt ma przypisaną mu podkategorię, a każda podkategoria należy do pewnej kategorii. W ten sposób każdy produkt ma określoną kategorię. Aby odnaleźć kategorię produktu, musimy przejść przez łańcuch dwóch relacji. Rysunek 1-3 zawiera przykład łańcucha złożonego z trzech relacji, zaczynającego się od tabeli Sales (sprzedaż) i prowadzącego do tabeli Product Category.
Linia stanowiąca symbol każdej relacji może zawierać jedną lub dwie strzałki, określające kierunek filtrowania. Na rysunku 1-3 można zauważyć dwie strzałki w relacji pomiędzy tabelami Sales i Product, podczas gdy wszystkie inne relacje mają tylko pojedyncze. Strzałka wskazuje kierunek automatycznego filtrowania relacji (cross filter). Zagadnienie to omówimy szczegółowo w dalszych rozdziałach, gdyż ustalenie właściwego kierunku filtrowania jest jedną z najważniejszych umiejętności, które trzeba opanować. Zazwyczaj będziemy zniechęcać do stosowania filtrów dwukierunkowych. W tym modelu zostały one pokazane tylko w celach edukacyjnych.
Model danych może mieć znaczący wpływ na wyniki naszego kodu DAX. Ten sam kod, ale wykonany w różnych modelach, daje odmienne wyniki. Tym samym podczas pisania formuł DAX deweloperzy powinni bardzo dobrze znać swój model danych. Chcielibyśmy przedstawić prosty przykład tej koncepcji, zmieniając kierunek filtrowania relacji, aby pokazać, że w efekcie ten sam kod DAX zwróci odmienne wyniki.
Każda relacja może mieć jeden lub dwa kierunki filtrowania. Filtrowanie zawsze występuje od strony jednoznacznej w kierunku strony wieloznacznej relacji. Jeśli relacja jest dwukierunkowa (czyli jej symbol zawiera dwie strzałki), wówczas filtrowanie może zachodzić również od strony wieloznacznej do jednoznacznej.
Utworzymy raport oparty na modelu z rysunku 1-3, umieszczając lata w wierszach, zaś miary Sum of Quantity i Count of Product Name w obszarze wartości. Oto kod dla tych dwóch miar:
Sum of Quantity = SUM ( Sales[Quantity] )
Count of Product Name = COUNT ( 'Product'[Product Name] )
Nawet dla kogoś, kto jest zupełnym nowicjuszem w języku DAX, powyższy kod jest oczywisty i tworzy wyniki pokazane na rysunku 1-4.
Rysunek 1-4 Raport pokazujący efekt filtrowania przez wiele tabel.
Year to kolumna należąca do tabeli Date. Tabela ta jest jednoznaczną stroną relacji z tabelą Sales (sprzedaż). Zatem gdy umieściliśmy Quantity w tabeli przestawnej, silnik filtruje tabelę Sales według lat. To dlatego pokazywane ilości są przefiltrowane przez lata.
W przypadku tabeli Product sytuacja jest nieco inna. Relacja pomiędzy tabelami Sales i Product jest dwukierunkowa; umieszczenie nazw produktów w tabeli przestawnej zwraca jako wynik liczbę produktów sprzedanych w każdym roku. Inaczej mówiąc, filtrowanie według lat propagowane jest do tabeli Product poprzez łańcuch relacji. Gdyby relacja pomiędzy tabelami Sales i Product była jednokierunkowa, wynik byłby inny.
Rysunek 1-5 pokazuje model, w którym relacja pomiędzy tabelami Product i Sales została zmieniona na jednokierunkową.
Rysunek 1-5 Teraz relacja pomiędzy Sales i Product jest jednokierunkowa.
Wprawdzie kod DAX pozostał taki sam, jednak zmieniliśmy propagację filtrowania, co zmienia wyniki raportu, jak widać na rysunku 1-6.
Rysunek 1-6 Kolumna Count of Product Name pokazuje teraz tę samą wartość powtórzoną w każdym wierszu.
Jak widzimy, wyniki naszego kodu DAX, pomimo jego prostoty, zależą od struktury modelu. Jak dowiemy się w dalszej części książki, projektowanie modelu danych z użyciem relacji dwukierunkowych rzadko kiedy jest właściwym działaniem. Zależnie od aktualnego scenariusza trzeba wybrać odpowiednią propagację relacji. Postępując zgodnie z sugestiami, które przedstawiamy w tej książce, będziemy w stanie zminimalizować dwukierunkowe filtrowanie modelu tak bardzo, jak to możliwe, włączając je tylko w określonych miarach i wtedy, gdy jest to konieczne.
Przedstawiamy demonstracyjny model danych
W większej części tej książki będziemy używać prostego modelu danych reprezentującego biznes fikcyjnej firmy Contoso. Firma ta sprzedaje sprzęt elektroniczny na całym świecie poprzez fizyczne sklepy oraz online. Dla każdej transakcji baza danych zawiera informacje o sklepie, dacie zamówienia, dacie dostawy, kliencie i sprzedanym produkcie.
Model składa się z pięciu tabel pokazanych na rysunku 1-7.
Rysunek 1-7 Model danych Contoso składa się z pięciu tabel: Customer, Store, Sales, Date oraz Product.
Tabela Sales zawiera indywidualne sprzedaże produktów. Kiedy klient składa zamówienie z wieloma produktami, można je zgrupować za pomocą kolumny Sales[Order Number] (numer zamówienia). Jednak większość analiz, jakie będziemy tworzyć, nie wymaga numeru zamówienia.
Model danych jest zgodny z metodologią Kimballa i wykorzystuje schemat gwiazdy, gdzie Sales służy jako tabela faktów, zaś pozostałe tabele pełnią funkcje wymiarów. Jeśli ktoś nie zna koncepcji schematu gwiazdy, nie powinno to stanowić żadnej przeszkody z punktu widzenia tej książki. W takim przypadku jednak zdecydowanie rekomendujemy dodanie poznawania schematów gwiaździstych i modelowania do swoich planów edukacyjnych w nieodległej przyszłości. Pisanie kodu DAX dla modelu, który nie jest dobrze zaprojektowany, może być bardzo bolesnym doświadczeniem. Z naszej praktyki wynika, że większość problemów z formułami DAX wynika z problemów samego modelu danych. Uczciwie mówiąc, lepiej być dobrym w modelowaniu danych i przeciętnym autorem kodu DAX, niż zostać guru języka DAX, ale nie radzić sobie z modelowaniem. Dlatego jeśli modelowanie danych nie jest twoją pasją, zakasaj rękawy i zacznij się tego uczyć, chociażby w przerwach podczas czytania tej książki. W ten sposób zdołasz osiągnąć na raz dwa ważne cele. Jako punkt wyjścia możemy (bezwstydnie) polecić odwiedziny w witrynie www.sqlbi.com i odszukanie kursu wideo "Data Modeling for Power BI".
Co powiedziawszy, w tej książce nie będziemy szczegółowo omawiać modelowania danych, gdyż ważna jest tylko wiedza, że demonstracyjna baza danych ma schemat gwiazdy. Czasami jednak będziemy odwoływać się do Sales jako "tabeli faktów", a do pozostałych tabel jako "wymiarów".
Jeśli wrócimy na chwilę do rysunku 1-7, możemy zauważyć, że pomiędzy tabelami Sales i Date widoczne są dwie relacje. Wynika to stąd, że Sales zawiera dwie kolumny dat: Order Date (data zamówienia) oraz Delivery Date (data dostawy). Tym samym tabeli Date można używać do filtrowania albo po dacie zamówienia, albo dostawy. W przypadku istnienia dwóch relacji pomiędzy tymi samymi tabelami tylko jedna może być aktywna w danym momencie. Domyślnie jest to ta, która używa daty zamówienia. W dalszych rozdziałach dowiemy się, jak aktywować nieaktywną relację na żądanie i jak wykorzystywać relacje nieaktywne.
Model danych Contoso, choć prosty, jest wystarczający dla większości przykładów przedstawionych w książce. W kilku rzadkich scenariuszach możemy potrzebować innej wersji bazy danych albo zupełnie odmiennego modelu danych, gdy będziemy pokazywać formuły DAX, które nie byłyby skuteczne w tak prostym modelu. W tych przypadkach objaśnimy ten unikatowy model użyty do demonstracji. W razie braku jakiejkolwiek wzmianki na ten temat można przyjąć, że pokazana demonstracja działa w modelu Contoso.
Zagadnienia wydajności wykraczają poza tematykę tej książki; z tego powodu wykorzystujemy bardzo niewielki model danych, w którym tabela faktów (Sales) zawiera około 13000 wierszy (jest to największa tabela w modelu).
Dlaczego DAX jest odmienny od dowolnego innego języka
Większość naszych czytelników zapewne zetknęła się już z innymi językami programowania, takimi jak SQL, MDX, Python, R, C# lub jakimś innym, mniej rozpowszechnionym. Być może też nie jesteś programistą; być może wywodzisz się ze świata analityki danych, finansów lub dowolnej innej aktywności biznesowej, w której dane są ważne. Choć może to nie jest oczywiste, prawie na pewno znasz przynajmniej jeden język programowania: język formuł Excela. Tak, język formuł Excela zasadniczo jest językiem programowania!
Rozpowszechnionym, acz błędnym przekonaniem jest, że znajomość innego języka programowania w jakiś sposób zapewnia przewagę przy nauce DAX. Nieszczęśliwie, choć tak mówi intuicja, prawdziwe jest twierdzenie odwrotne: wcześniejsza znajomość jakiegoś języka programowania zapewne okaże się największym obciążeniem. Mówimy tak na podstawie własnych doświadczeń. W ciągu lat opanowaliśmy szaloną liczbę języków programowania, w tym asembler, Fortran, Basic, R, Prolog, Forth, Lisp, Cobol, C, C#, Pascal, MDX i jeszcze kilka innych. To prawda, że nauka C# jest łatwa, jeśli już znamy C i Pascal, gdyż, pomimo zmian składni, ogólna architektura tych języków jest taka sama. Dla przykładu jesteśmy w stanie przeczytać jakiś kawałek kodu w Javie i zrozumieć, co robi, a może nawet go debugować, pomimo że żaden z nas nie zajmuje się programowaniem w tym języku.
Jednak DAX jest odmienny od każdego innego języka programowania, który możesz już znać. Nauka DAX wymaga dogłębnego zrozumienia nowych i unikatowych koncepcji, takich jak konteksty wykonania, przejście kontekstu oraz interakcja pomiędzy modelem danych a naszym kodem. Te pojęcia są zupełną nowością wprowadzoną w języku DAX i poznawanie, badanie i zrozumienie wymaga znaczącego czasu.
W kolejnych kilku punktach przedstawimy wskazówki dla czytelników, którzy mają doświadczenie w programowaniu w takich językach, jak Microsoft Excel (chodzi o jego język formuł, a nie VBA), SQL i Python. Każdy punkt skupia się na konkretnym języku, pozwalając zaciekawionym czytelnikom szybko porównać go z DAX. Jeśli takie porównanie wydaje się pomocne, przeczytaj odpowiedni podrozdział. W przeciwnym razie możesz spokojnie pominąć resztę tego podrozdziału i przejść wprost do "Jak stać się mistrzem DAX".
DAX dla użytkowników Excela
Istnieje spore prawdopodobieństwo, że znasz już język formuł programu Excel, do którego DAX jest w pewnym stopniu podobny. Ostatecznie korzenie języka DAX tkwią w rozszerzeniu Power Pivot for Excel, zaś projektujący je zespół starał się zachować podobieństwo obydwu języków, aby ułatwić użytkownikom przejście do nowego języka. Niemniej jednak istnieje pomiędzy nimi kilka bardzo istotnych różnic.
Komórki kontra tabele
W Excelu obliczenia są wykonywane względem komórek. Komórka wskazywana jest przy użyciu jej współrzędnych. Oznacza to, że pisane przez nas formuły wyglądają podobnie do pokazanej poniżej:
= (A1 * 1.25) - B2
W języku DAX nie istnieje pojęcie komórki ani jej współrzędnych. DAX odwołuje się do tabel i kolumn, a nie komórek. Tak więc w wyrażeniach DAX pojawią się odniesienia tylko do tabel i zawartych w nich kolumn. Koncepcje tabel i kolumn nie są niczym nowym dla użytkowników Excela. W rzeczywistości, jeśli zdefiniujemy zakres komórek jako tabelę, używając polecenia Format as a Table (Formatuj jako tabelę), można pisać wyrażenia, które odwołują się właśnie do nazw tabel i kolumn. Na rysunku 1-8 można zauważyć, że kolumna SalesAmount wylicza wyrażenie, które odwołuje się do kolumn w tej samej tabeli, a nie do określonych współrzędnymi komórek arkusza.
Rysunek 1-8 W Excelu również można używać nazw kolumn.
W formułach Excela odwołujemy się do kolumn w tabeli poprzez format [@NazwaKolumny], gdzie NazwaKolumny jest nazwą (wpisem w nagłówku) kolumny, której chcemy użyć, zaś symbol @ oznacza "użyj wartości dla bieżącego wiersza". Choć składnia ta nie jest zbyt intuicyjna, zwykle nie musimy pisać takich wyrażeń. Pojawiają się one po prostu po kliknięciu komórki, a Excel sam zajmie się wstawieniem do niej odpowiedniego kodu.
Możemy więc myśleć, że Excel udostępnia dwie różne metody wykonywania obliczeń: możemy posługiwać się zwykłymi odwołaniami do komórek (w tym przypadku formuła dla komórki F4 przyjmie postać E4*D4) albo użyć odniesień do kolumn, o ile pracujemy wewnątrz tabeli. Wykorzystanie odniesień do kolumn ma tę zaletę, że można wówczas użyć dokładnie tego samego wyrażenia we wszystkich komórkach kolumny, a Excel obliczy formułę dla różnych wartości w każdym wierszu.
Język DAX pracuje tylko na tabelach, zatem wszystkie formuły muszą odnosić się do kolumn. Na przykład pokazane wcześniej mnożenie zostanie zapisane w języku DAX w następujący sposób:
Sales[SalesAmount] = Sales[ProductPrice] * Sales[ProductQuantity]
Jak widać, każda kolumna jest prefiksowana nazwą tabeli, do której należy. W Excelu nie podajemy nazwy tabeli, gdyż formuły Excela działają tylko w obrębie pojedynczej tabeli. W języku DAX, przeciwnie, odniesienie musi wskazywać nazwę tabeli, gdyż DAX działa w modelu danych zawierającym wiele tabel, przy czym kolumny z różnych tabel mogą mieć (i często mają) takie same nazwy.
Wiele funkcji DAX działa tak samo, jak odpowiadające im funkcje Excela. Na przykład funkcja IF wygląda niemal dokładnie tak samo w języku DAX i w Excelu1:
Excel: IF ( [@SalesAmount] > 10, 1, 0)
DAX: IF ( Sales[SalesAmount] > 10, 1, 0)
Ważnym aspektem odróżniającym składnię formuły Excel i jej odpowiednik w języku DAX jest sposób odwoływania się do całej kolumny. Można zauważyć, że w zapisie [@ProductQuantity] znak @ oznacza "wartość w bieżącym wierszu". Przy posługiwaniu się językiem DAX nie musimy tego wskazywać. Domyślne zachowanie języka polega na pobraniu wartości z bieżącego wiersza. Jeśli w Excelu chcemy odwołać się do całej kolumny (właśnie tak, do wszystkich wierszy w tej kolumnie), osiągamy to, usuwając symbol @, co można zauważyć na rysunku 1-9.
Wartość w kolumnie AllSales jest taka sama we wszystkich wierszach, gdyż jest to całkowita suma kolumny SalesAmount. Innymi słowy, mamy tu składniowe rozróżnienie wartości dla danej kolumny w bieżącym wierszu oraz wartości kolumny jako całości.
W języku DAX wygląda to inaczej. W tym przypadku wyrażenie AllSales pokazane na rysunku 1-9 należy napisać w ten sposób:
[AllSales] = SUM ( Sales[SalesAmount] )
Jak widać, nie istnieje różnica składniowa pomiędzy odwołaniem się do kolumny w celu pobrania wartości dla określonego wiersza a użyciem wszystkich wartości z tej kolumny. DAX rozumie, że chcemy zsumować wszystkie wartości z kolumny, gdyż użyliśmy jej nazwy wewnątrz agregatora (w tym przypadku funkcji SUM), który wymaga przekazania nazwy kolumny jako parametru. Tak więc, podczas gdy Excel wymaga jawnego rozróżnienia (odpowiedniej składni) pomiędzy dwoma sposobami odczytywania danych, DAX dokonuje tego rozróżnienia w sposób automatyczny. Może to być mylące i wymaga zmiany sposobu myślenia - przynajmniej początkowo.
Rysunek 1-9 W Excelu można odwołać się do całej kolumny, pomijając symbol @ przed jej nazwą.
Excel i DAX: dwa języki funkcyjne
Podobieństwo pomiędzy obydwoma językami wynika z faktu, że zarówno język formuł Excela, jak i DAX są językami funkcyjnymi. Język funkcyjny jest zbudowany z wyrażeń, które są - zasadniczo - wywołaniami funkcji. Ani w Excelu, ani w DAX nie występują koncepcje poleceń, pętli czy skoków, które są powszechne w większości języków programowania. W języku DAX wszystko jest wyrażeniem. Ten aspekt języka stanowi często wyzwanie dla programistów mających doświadczenie z innymi językami, ale nie powinien być niczym zaskakującym dla użytkowników Excela.
Korzystanie z iteratorów
Jedną z koncepcji, które mogą być nowością, są iteratory. Przy pracy w Excel przyzwyczailiśmy się do wykonywania obliczeń po jednym kroku na raz. W poprzednim przykładzie można zauważyć, że aby wyliczyć całkowitą wartość sprzedaży, najpierw utworzyliśmy kolumnę zawierającą cenę pomnożoną przez ilość sprzedanych elementów, a następnie, w drugim kroku, zsumowaliśmy tę kolumnę. Uzyskaną liczbę można teraz wykorzystać na przykład jako mianownik do wyliczenia procentowego udziału każdego produktu w sprzedaży.
Używając języka DAX można wykonać tę samą operację w jednym kroku poprzez użycie iteratora. Iterator robi dokładnie to, co sugeruje jego nazwa: iteruje tabelę (przechodzi przez kolejne wiersze) i wykonuje obliczenia dla każdego wiersza, agregując rezultaty, aby wytworzyć pojedynczą wartość końcową.
Sumę wszystkich wartości sprzedaży z poprzedniego przykładu można wyliczyć, używając iteratora SUMX:
[AllSales] =
SUMX (
Sales,
Sales[ProductQuantity] * Sales[ProductPrice]
)
W podejściu tym można zauważyć zarówno korzyści, jak i niewygody. Zaletą jest to, że można wykonać wiele złożonych obliczeń w jednym kroku bez konieczności zajmowania się tworzeniem wielu dodatkowych kolumn, które są przydatne tylko do celów wyliczenia pewnych szczególnych formuł. Wadą jest to, że programowanie w DAX jest mniej oczywiste, niż w Excelu. W rzeczywistości nie widzimy kolumny obliczającej cenę pomnożoną przez ilość; istnieje ona tylko wirtualnie, podczas wykonywania obliczeń.
Prawdę mówiąc, nadal mamy opcję utworzenia obliczanej kolumny, która będzie zawierała iloczyn ceny i ilości. Tym niemniej, jak dowiemy się później, rzadko jest to dobra praktyka, gdyż zużywa cenną pamięć i może spowolnić działanie wszystkich obliczeń.
DAX wymaga nieco teorii
Powiedzmy to jasno: to, że DAX wymaga od użytkownika studiowania teorii, nie jest różnicą pomiędzy językami programowania; różnica leży w sposobie myślenia. Podobnie jak każdy inny mieszkaniec tej planety, zapewne często przeglądasz sieć w poszukiwaniu złożonych formuł i wzorów rozwiązań dla scenariuszy, które próbujesz rozwiązać. Przy korzystaniu z Excela są spore szanse, że znajdziemy formułę, która robi niemal dokładnie to, co trzeba. Wystarczy skopiować formułę, dostosować ją do swoich potrzeb i następnie jej użyć, nie zastanawiając się zbytnio, jak działa.
Podejście to, które sprawdza się w Excelu, nie działa w przypadku DAX. Konieczne jest przestudiowanie pewnej ilości teorii i dokładne zrozumienie, jak działają konteksty wykonania, zanim posiądzie się umiejętność pisania dobrego kodu DAX. Bez właściwych podstaw teoretycznych DAX albo będzie działać jak magia, albo będziemy otrzymywać dziwne liczby, które nie mają żadnego sensu. Problemem nie jest jednak język DAX, ale fakt, że użytkownik nie zrozumiał jeszcze, jak on działa.
Szczęśliwie teoria leżąca u podstaw DAX ograniczona jest do kilku ważnych koncepcji, które przedstawimy w rozdziale 2. Gdy opanujesz pojęcie kontekstu wykonania, DAX nie będzie już miał przed tobą tajemnic i dalsza nauka będzie polegała głównie na zdobywaniu doświadczenia. Jednak nie powinieneś próbować pójść dalej, dopóki nie opanujesz dobrze tego kawałka teorii. Przypomnę: wiedza to połowa zwycięstwa!
Sugerujemy przeczytanie co najmniej pierwszych 10 rozdziałów tej książki po kolei, wykonując ćwiczenia na plikach przykładowych i pisząc miary, bez pomijania żadnego podpunktu. W ten sposób uzyskasz sprawność w posługiwaniu się kontekstami wykonania i będziesz w stanie lepiej zrozumieć formuły DAX - także te, które już napisałeś - dokonując rzeczywistych postępów w swojej nauce języka DAX.
DAX dla programistów SQL
Jeśli posługujesz się językiem SQL, pracowałeś już z wieloma tabelami i tworzyłeś między nimi połączenia, aby ustanowić relacje. Z tego punktu widzenia powinieneś poczuć się znajomo w świecie DAX, gdyż przetwarzanie w DAX polega na odpytywaniu zbioru tabel powiązanych relacjami i agregowaniu wartości.
Obsługiwanie relacji
Pierwszą różnicą pomiędzy SQL a DAX jest sposób działania relacji w modelu. W środowisku SQL możemy zdefiniować ograniczenia obcego klucza, aby zadeklarować relacje pomiędzy tabelami, ale silnik bazy danych nigdy nie użyje tych kluczy w zapytaniach, o ile nie wskażemy ich jawnie. Jeśli na przykład mamy tabelę Customer oraz tabelę Sales, przy czym kolumna CustomerKey jest kluczem głównym tabeli Customer i kluczem obcym w tabeli Sales, możemy napisać zapytanie podobne do poniższego:
SELECT
Customers.CustomerName, SUM ( Sales.SalesAmount ) AS SumOfSales
FROM
Sales
INNER JOIN Customers
ON Sales.CustomerKey = Customers.CustomerKey
GROUP BY
Customers.CustomerName
Choć zadeklarowaliśmy relację pomiędzy tabelami w naszym modelu, używając obcych kluczy, nadal musimy jawnie określić warunek złączenia w zapytaniu. Choć to sprawia, że zapytania są nieco bardziej rozbudowane, jest to przydatne, bo pozwala użyć różnych warunków złączenia w różnych zapytaniach, co daje wielką swobodę w sposobie budowania zapytań.
W języku DAX relacje są częścią modelu i wszystkie są typu LEFT OUTER JOIN (lewe złączenia zewnętrzne). Po zdefiniowaniu w modelu nie musimy już deklarować typu złączenia w zapytaniu: DAX automatycznie użyje LEFT OUTER JOIN w zapytaniu, o ile użyjemy kolumn odwołujących się do tabeli głównej. Tak więc poprzednie zapytanie SQL można w DAX zapisać następująco:
EVALUATE
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", SUM ( Sales[SalesAmount] )
)
Ponieważ DAX zna istniejącą relację pomiędzy tabelami Sales i Customers, wykona automatycznie złączenie zgodne z modelem. Na koniec funkcja SUMMARIZECOLUMNS potrzebuje wykonać grupowanie według kolumny Customers[CustomerName], ale nie potrzebujemy do tego żadnego specjalnego słowa kluczowego: SUMMARIZECOLUMNS automatycznie grupuje dane według wybranych kolumn.
DAX jest językiem funkcyjnym
SQL jest językiem deklaratywnym. To, czego potrzebujemy, definiujemy poprzez deklarowanie zbioru danych do odczytania przy użyciu wyrażeń SELECT, nie zastanawiając się, jak silnik faktycznie będzie pobierał potrzebne informacje. Język DAX jest natomiast językiem funkcyjnym.
W DAX każde wyrażenie jest wywołaniem funkcji, a parametry tej funkcji mogą być wywołaniami kolejnych funkcji. Przetwarzanie parametrów może prowadzić do bardzo złożonych planów zapytań, które DAX wykonuje w celu obliczenia wyniku.
Na przykład, aby pobrać tylko dane klientów, którzy mieszkają w Europie, moglibyśmy napisać takie zapytanie w SQL:
SELECT
Customers.CustomerName,
SUM ( Sales.SalesAmount ) AS SumOfSales
FROM
Sales
INNER JOIN Customers
ON Sales.CustomerKey = Customers.CustomerKey
WHERE
Customers.Continent = 'Europe'
GROUP BY
Customers.CustomerName
Przy korzystaniu z DAX nie deklarujemy warunku WHERE w zapytaniu. Zamiast tego trzeba użyć określonej funkcji (FILTER) do ograniczenia zwracanych wyników:
EVALUATE
SUMMARIZECOLUMNS (
Customers[CustomerName],
FILTER (
Customers,
Customers[Continent] = "Europe"
),
"SumOfSales", SUM ( Sales[SalesAmount] )
)
Można tu zauważyć, że FILTER jest funkcją: zwróci ona tylko tych klientów, którzy mieszkają w Europie, wytwarzając oczekiwany wynik. Kolejność zagnieżdżania kolejnych funkcji i rodzaje użytych funkcji mają wielki wpływ na finalny wynik, a także na wydajność działania. To samo zdarza się też w SQL, ale w tym przypadku ufamy, że optymalizator zapytań silnika SQL znajdzie optymalny plan zapytania. W języku DAX, choć optymalizator również wykonuje świetną pracę, na programiście spoczywa większa odpowiedzialność za napisanie dobrego kodu.
Podzapytania i warunki w DAX i SQL
Jedną z najsilniejszych funkcjonalności SQL jako języka zapytań jest możliwość używania podzapytań. DAX dysponuje kilkoma podobnymi koncepcjami, choć w przypadku podzapytań wynikają one w sposób naturalny z funkcyjnej natury języka.
Na przykład, aby odczytać dane klientów i łączny wynik sprzedaży jedynie tych klientów, którzy dokonali zakupu za więcej niż 100 USD, możemy napisać następujące zapytanie SQL:
SELECT
CustomerName,
SumOfSales
FROM (
SELECT
Customers.CustomerName,
SUM ( Sales.SalesAmount ) AS SumOfSales
FROM
Sales
INNER JOIN Customers
ON Sales.CustomerKey = Customers.CustomerKey
GROUP BY
Customers.CustomerName
) AS SubQuery
WHERE
SubQuery.SumOfSales > 100
Ten sam rezultat możemy uzyskać w DAX, po prostu zagnieżdżając wywołanie funkcji:
EVALUATE
FILTER (
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", SUM ( Sales[SalesAmount] )
),
[SumOfSales] > 100
)
W tym kodzie podzapytanie odczytujące CustomerName oraz SumOfSales jest następnie przekazywane do funkcji FILTER, która zwraca tylko te wiersze, w których SumOfSales jest większa niż 100. Na razie ten kod może wydawać się nieczytelny, ale niedługo, gdy już zaczniemy uczyć się języka DAX, będzie można zauważyć, że korzystanie z podzapytań jest znacznie prostsze, niż w SQL, i że przebiega naturalnie dzięki temu, że DAX jest językiem funkcyjnym.
DAX dla użytkownika Pythona
Przy posługiwaniu się Pythonem możemy używać SQL do złączania tabel i ładowania wyniku do pojedynczej ramki danych (DataFrame). W takim przypadku istotny jest również poprzedni punkt dotyczący SQL. W tym punkcie skupimy się na porównaniu stylu kodowania Pythona i DAX przy manipulowaniu danymi przechowywanymi w różnych obiektach DataFrame lub tabelach.
Łączenie i agregowanie tabel
Operacja scalania (merge) wykonywana na DataFrame jest podobna do operacji JOIN w SQL, gdyż kod jawnie odwołuje się do kolumn definiujących logiczną relację pomiędzy dwiema encjami. Poniższy przykład pokazuje, jak połączyć Sales i Customers używając CustomerKey jako klucza i zgrupować wyniki według CustomerName, pokazując w zmiennej SumOfSales agregację wartości Quantity pomnożonej przez Net Price wiersz po wierszu:
# Tworzenie kopii danych sprzedaży i obliczenie wartości sprzedaży
# na poziomie wiersza
self.sales_df['SalesAmount'] = \
self.sales_df['Quantity'] * self.sales_df['Net Price']
# Scalenie danych sprzedaży z danymi klientów według CustomerKey
merged_df = self.sales_df.merge(
self.customer_df[['CustomerKey', 'CustomerName']],
on='CustomerKey', how='left', copy=False
)
# Grupowanie według CustomerName i sumowanie obliczonych wartości
# sprzedaży
result_df = (merged_df
.groupby('CustomerName')['SalesAmount']
.sum()
.reset_index()
.rename(columns={'SalesAmount': 'SumOfSales'})
.sort_values('SumOfSales', ascending=False))
W DAX relacje pomiędzy tabelami są częścią modelu semantycznego. To oraz inne automatyczne przełączania kontekstu, które występują pomiędzy różnymi konstruktami, sprawiają, że nieco rozwlekły kod w Pythonie ma dużo krótszy odpowiednik w DAX:
EVALUATE
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", SUMX ( Sales, Sales[Quantity] * Sales[Net Price] )
)
Inną różnicą pomiędzy tymi językami (co można zauważyć w powyższych przykładach) to fakt, że Python używa jawnych operacji groupby, w których zawsze musi zostać wyspecyfikowana jakaś agregacja (taka jak sum). I przeciwnie, w DAX wyrażenia używane w SUMMARIZECOLUMNS (i w ogólności miary zdefiniowane w modelu semantycznym) logikę agregacji zawierają w samym wyrażeniu bez konieczności powtarzania jej w składni grupującej.
Dla przykładu, poniższy kod pokazuje, że SUMMARIZECOLUMNS oblicza miarę Sales Amount zagregowaną dla każdej wartości CustomerName; jednak działająca w tle logika miary Sales Amount nie jest dołączana do samego wyrażenia zapytania - czyli kodu po EVALUATE. Zamiast tego jest to miara zdefiniowana w modelu semantycznym, podobnie jak tabele i relacje, choć w tym fragmencie kodu została dołączona jako część zapytania dla poprawy czytelności:
DEFINE
MEASURE Sales[Sales Amount] = SUM ( Sales, Sales[Quantity] * Sales[Net Price] )
EVALUATE
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", [Sales Amount]
)
Struktura zapytań i manipulowanie filtrami
Istnieje wiele sposobów stosowania filtrów podczas przetwarzania obiektu DataFrame. W tym przykładzie filtr jest stosowany do ramki danych customers, przed jej złączeniem z ramką sales:
# Najpierw filtrujemy klientów według kontynentów
customers_in_region = self.customer_df[
self.customer_df.Continent == "Europe"
][['CustomerKey', 'CustomerName']]
# Scalenie sales z customer według CustomerKey
merged_df = self.sales_df.merge(customers_in_region, on='CustomerKey',
how='inner',copy=False)
# Grupowanie według CustomerName i sumowanie obliczonych sprzedaży
result_df = (merged_df
.groupby('CustomerName')['SalesAmount']
.sum()
.reset_index()
.rename(columns={'SalesAmount': 'SumOfSales'})
.sort_values('SumOfSales', ascending=False))
W języku DAX możemy wyspecyfikować filtr bez zastanawiania się, do której tabeli powinien zostać zastosowany. Predyktat filtru jest stosowany do kontekstu wykonania, dzięki czemu silnik DAX może znaleźć optymalny plan wykonania zapytania:
EVALUATE
CALCULATETABLE (
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", [Sales Amount]
),
Customers[Continent] = "Europe"
)
Jednak jeśli potrzebujemy w Pythonie przefiltrować wyniki agregacji, możemy włączyć funkcję query po agregacji, a tym samym mamy kontrolę nad tym, kiedy filtr zostanie zastosowany:
result_df = (merged_df .groupby('CustomerName')['SalesAmount']
.sum()
.reset_index()
.rename(columns={'SalesAmount': 'SumOfSales'})
.query('SumOfSales > 100')
.sort_values('SumOfSales', ascending=False))
Analogicznie w DAX możemy przefiltrować wynik dowolnego wyrażenia tablicowego, używając funkcji FILTER:
EVALUATE
FILTER (
CALCULATETABLE (
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", [Sales Amount]
),
Customers[Continent] = "Europe"
),
[SumOfSales] > 100
)
W Pythonie zazwyczaj mamy listę kolejnych operacji, które krok po kroku budują wyniki. Takie podejście krok po kroku można zaimplementować w DAX, wykorzystując zmienne i nazywając każdy krok samo-objaśniającą się nazwą:
EVALUATE
VAR SalesInEurope =
CALCULATETABLE (
SUMMARIZECOLUMNS (
Customers[CustomerName],
"SumOfSales", [Sales Amount]
),
Customers[Continent] = "Europe"
)
VAR Result =
FILTER (
SalesInEurope,
[SumOfSales] > 100
)
RETURN
Result
DAX dla użytkowników Power BI
Jeśli pominąłeś poprzednie podpunkty i przeszedłeś od razu tutaj, witaj! DAX jest natywnym językiem Power BI i jeśli nie masz wcześniejszych doświadczeń związanych z Excelem, SQL ani MDX, Power BI będzie pierwszym i podstawowym miejscem, w którym poznawałeś DAX. Jeśli nie masz wcześniejszych doświadczeń związanych z budowaniem modeli za pomocą innych narzędzi, dowiesz się, że Power BI jest wydajnym narzędziem modelowania i analizy biznesowej, zaś DAX jest jego doskonałym dopełnieniem.
Być może zacząłeś używać Power BI jakiś czas temu, a teraz chcesz przejść na wyższy poziom zaawansowania. Jeśli tak jest, przygotuj się na fascynującą podróż po możliwościach języka DAX.
Jak stać się mistrzem DAX
Język DAX jest prosty, ale nie jest łatwy. Powtarzamy to zdanie niemal na każdym kursie, jaki prowadziliśmy przez te wszystkie lata, na konferencjach, spotkaniach grup użytkowników... to jest mantra. Jest to dobra synteza tego, czym jest DAX.
Pytanie, które często słyszymy, brzmi: "Dlaczego DAX nie jest łatwy, skoro jest prosty?" Rozwińmy nieco ten temat, dzieląc się naszym doświadczeniem z wykładania języka DAX i by wyjaśnić, dlaczego - dla wielu - DAX to coś w rodzaju czarnej magii, opanowanej przez nielicznych czarodziejów i przekazywanej ich głosem do niewiele liczniejszych, oświeconych adeptów.
Prawda jest zupełnie inna: każdy (no, prawie) może nauczyć się języka DAX. U nas w SQLBI nie jesteśmy czarodziejami. A może jesteśmy, ale w takim razie pragniemy się dzielić naszymi sekretami, aby każdy czytelnik mógł dołączyć do sekretnego kręgu deweloperów DAX!
DAX jako język funkcyjny
Języki funkcyjne, z punktu widzenia matematyki, są niebywale eleganckie. Niestety, są również bardzo nieintuicyjne. Jako ludzie, opisując algorytm podajemy kolejne kroki. Nie podajemy funkcji, która oblicza wynik. Przedstawiamy kroki prowadzące do tego wyniku.
Wyobraź sobie, że potrzebujesz złożyć nowy mebel - niech to będzie krzesło - który właśnie przywieziono ci do domu. Otwierasz instrukcję i znajdujesz to:
KRZESŁO =
DODAJ (
UŻYJŚRUB ( Oparcie ),
POWTÓRZ (
4,
DODAJ (
UŻYJŚRUB ( Noga ),
Siedzenie
)
)
)
Trochę to przytłaczające, prawda? Jeśli przełożymy to z formy funkcyjnej na iteracyjną, okaże się, że trzeba przymocować (dodać) nogę do siedzenia używając śrub; proces ten trzeba powtórzyć dla wszystkich czterech nóg. Kiedy to ukończymy, trzeba użyć dodatkowych śrub, aby przykręcić oparcie do siedzenia. Et voila, mamy krzesło!
Choć mam nadzieję, że nikt nigdy nie przekaże mi instrukcji montażowych w formie funkcyjnej, w przypadku DAX trzeba nauczyć się myśleć funkcyjnie. Osiągnięcie tej umiejętności wymaga czasu i wysiłku.
Języki funkcyjne jako takie nie są niczym nowym. Są z nami od czasów LISPa, prehistorii informatyki. Nigdy nie zdobyły większej popularności, głównie dlatego, że są tak trudne do opanowania. Nie zmienia to jednak faktu, że algorytm wyrażony w języku funkcyjnym można optymalizować dużo łatwiej, niż ten sam algorytm sformułowany w języku iteracyjnym - przez co rozumiem instrukcje krok po kroku, czego oczekiwalibyśmy dla mebli do samodzielnego montażu. To, że DAX jest funkcyjny, jest częścią ceny, jaką musimy zapłacić za jego szybkość i modularność.
Jeśli komuś trudno sobie poradzić z funkcyjną naturą DAX, jak mógłby poprawić swoje wrażenia? Cóż, trzeba ćwiczyć, ćwiczyć, a potem znowu ćwiczyć. W pewnym momencie twój mózg zacznie odczytywać języki funkcyjne. Jedyne, co może w tym pomóc, są ćwczenia. "Myślenie funkcyjne" jest złożone i sprzeczne z intuicją; trening to nasza jedyna broń.
Trening czyni mistrza
DAX jest tym, czym się zajmujemy. Spędzamy całe dnie pisząc w nim kod i ucząc innych tego języka. Całe dnie i każdego dnia. Tylko w taki sposób możesz zostać mistrzem: ćwicząc. W przypadku DAX oznacza to mnóstwo ćwiczeń!
Jeśli chodzi o Power BI, większość użytkowników nie spędza całego swojego czasu na przygotowywaniu codziennych raportów. To oczywiście słuszne: raport ma na celu przedstawić jakieś liczby, a użytkownicy mają z tych liczb wyciągać wnioski, a nie tylko je produkować. Dowolny zwykły użytkownik może potrzebować DAX podczas przygotowywania raportu, ale to tylko nieznaczny ułamek jego codziennej pracy. Co zatem się dzieje, gdy masz kilka dni, w których przygotowujesz raport, a potem następują całe tygodnie, może nawet miesiące, w których nie napiszesz ani jednej linijki kodu, dopóki nie trzeba będzie uaktualnić tego raportu? Już wiadomo? Z wielkim wysiłkiem opanowałeś pewne konkretne szczegóły podczas tworzenia tego raportu, a po tygodniach bez ćwiczeń stwierdzasz, że niczego nie pamiętasz. Zaczynanie od początku jest frustrujące. Zapewne dasz sobie radę i tym razem, po czym znowu nastąpi kolejna runda, kolejne miesiące bez pisania kodu DAX. I tak w kółko.
Czy istnieje jakieś rozwiązanie, aby wyrwać się z tego błędnego koła? Tak. Trzeba po prostu włączyć DAX do swoich codziennych zajęć. Oznacza to, że jeśli pracujesz w zespole złożonym z wielu analityków, budowniczych raportów, możesz zostać ich ekspertem od DAX, co pozwoli ci spędzić więcej czasu na tworzeniu i doskonaleniu kodu, a mniej na analizowaniu liczb. Jeśli zaś nie możesz poświęcić całego dnia na taką pracę, postaraj się przynajmniej przeznaczyć trochę czasu co tydzień na pracę nad swoimi umiejętnościami. Nie pozwól, aby minęły miesiące bez wpisania pojedynczej formuły czy przyswojenia sobie nowego pomysłu.
Kilka ważnych koncepcji
Cały język DAX opiera się na zaledwie kilku koncepcjach:
Kontekst wykonania i propagowanie filtru
Iteratory
Przejście kontekstu
Tabele rozszerzone i wyrównywanie danych
Jeśli opanujesz te cztery koncepcji, staniesz się guru DAX. Jeśli zatrzymasz się na trzecim, możesz nazwać się ninją. Dysponując dwiema pierwszymi już będziesz solidnym deweloperem. Kluczem jest osiągnięcie w nich mistrzostwa, a nie jedynie zdobycie podstawowej wiedzy, czym są. Co więcej, są to koncepcje fundamentalne; nie ograniczają się do żadnych konkretnych funkcji.
Nauka języka DAX nie oznacza pamięciowego opanowania każdego szczegółu każdej funkcji. Można być prawdziwym guru bez znajomości znaczenia trzeciego argumentu RANKX. Jeśli chodzi o tego typu wiedzę szczegółową, zawsze możemy sięgnąć do strony https://dax.guide albo użyć wielu innych źródeł informacji w dowolnej chwili.
Żeby zostać mistrzem DAX, musisz dobrze poznać i zrozumieć podstawy. Muszą stać się twoją drugą naturą, dogłębnie. Poprzez ćwiczenia i tworzenie mnóstwa kodu, poprzez zrozumienie, co naprawdę dzieje się w tle. Nie przestawaj pracować nad formułą tylko dlatego, że już po prostu działa. Trzeba uzyskać pełne zrozumienie tego, jak ona działa. Na tym polega doskonalenie fundamentów DAX.
Bądź ciekawy! Nie pisz formuły, mając na celu jedynie uzyskanie jakiejś (poprawnej) liczby. Dąż do tego, aby naprawdę zrozumieć, jak ta formuła jest obliczana i czy jest naprawdę najlepszą drogą do obliczenia tej liczby. Jeśli twój kod generuje błąd, nie staraj się go poprawić, po prostu dodając CALCULATE tu czy tam, aż zacznie działać. Tak, wiemy. Właśnie tak robiliśmy wielokrotnie. Ale wiemy też, że trzeba pójść dalej. Zatem zatrzymaj się, uważnie przeczytaj swój kod i staraj się zrozumieć, co konkretnie jest w nim nie tak. I dopiero wtedy, gdy już dostrzeżesz problem, przychodzi czas, aby go naprawić.
DAX jest ściśle logiczny
Ten punkt jest konsekwencją poprzednich: DAX to język oparty na matematyce, zbudowany na kilku fundamentalnych koncepcjach. Gdyby DAX był nową teorią matematyczną, to byłoby doskonałe. Niestety, DAX nie jest teorią matematyczną: jest językiem używanym do budowania raportów. Nadal jednak jest oparty na tych samych koncepcjach, co teoria matematyczna. Jeśli jesteś bardzo logicznym typem osoby, zapewne pokochasz język DAX. Przez "bardzo logiczny typ" rozumiem kogoś, kto lubi spędzać godziny nad łamigłówkami. Jeśli jednak to nie jest twoja bajka, przyzwyczajenie się do DAX będzie wymagało nieco czasu.
Na szczęście istnieje prosty sposób uniknięcia tego problemu: wystarczy myśleć o DAX jak o grze. Początkowo nie próbuj go używać do rozwiązywania realnych problemów. Zamiast tego zacznij od prostych pomysłów, takich jak "Hej, bez względu na wszystko, chcę napisać prostą formułę obliczającą średni wiek moich klientów w chwili nabycia przez nich pierwszej pary butów w moim sklepie". Zapewne będzie trzeba godzin, a może i dni na próbach znalezienia rozwiązania. A jednak to będzie najbardziej owocny czas zainwestowany w naukę języka DAX. Sam siebie zmusisz do nauki podstaw w odważnej próbie osiągnięcia perfekcji.
Jeśli będziesz działał z nastawieniem "Potrzebuję tej wartości. Nie jest ważne, czy formuła jest elegancka lub szybka, po prostu chcę to obliczyć", to cóż, może się okazać, że ostatecznie zmarnujesz mnóstwo czasu. Próba nauki DAX poprzez samo rozwiązywanie praktycznych problemów nie jest dobrą drogą, aby to osiągnąć. Potrzebujesz teorii. Uczenie się teorii przez praktykę to znacznie trudniejszy sposób. Na szczęście, jeśli będziesz się uczyć DAX jako gry, będzie to świetna zabawa. Zaufaj nam, nauka DAX to jest zabawa - o ile robisz to dobrze!
Diabeł tkwi w szczegółach
Szczegóły... szczegóły wszędzie. Ludzie nie lubią drobnych detali; wolimy ogarnąć ogólny obraz, a szczegóły zostawić na potem. Niestety, DAX nie wybacza, jeśli przeoczysz jakiś drobny detal w formule. Zapomniałeś, że kontekst wiersza nie propaguje się przez relację? No cóż, dostałeś błędne wyniki. DAX pokręci nosem i pomyśli, że powinieneś był wiedzieć lepiej, mój przyjacielu. Nie pamiętałeś, że filtr tablicowy w CALCULATE operuje na tabeli rozszerzonej? No i co? Jedyne, czego DAX użyje, aby powiadomić cię o problemie, to niewłaściwy wynik. W tym zachowaniu nie ma nic zaskakującego: DAX oblicza to (i tylko to), o co go poprosiłeś. Jeśli zadajesz złe pytanie, dostajesz złą odpowiedź. Jasne i proste.
Ale dlaczego zadałeś złe pytanie? W ten sposób wracamy do fundamentalnej teorii języka DAX: musisz opanować podstawy. Jeśli poważnie zamierzasz zostać mistrzem DAX, musisz nie tylko zrozumieć teorię, ale ją opanować. Doskonałość w teorii osiągniesz wtedy, gdy przećwiczysz mnóstwo formuł i zrozumiesz dokładnie, jak poszczególnie kawałki łączą się ze sobą, kiedy wypróbujesz rozmaite wariacje tego samego kodu, aby pojąć, jak drobna zmiana wpływa na wyniki. Wtedy, gdy już nauczysz się tych szczegółów i po mnóstwie ćwiczeń, osiągniesz taki poziom biegłości, że pisanie formuł będzie przychodzić naturalnie. Pamiętaj jednak, że poprzeczkę na tej wysokości możesz umieścić dopiero wtedy, gdy już będziesz gotowy, aby awansować z DAX Pro na DAX ninja, a może nawet DAX guru.
Metoda prób i błędów nie działa w DAX
Wiele osób uczy się nowego języka, szukając w Google przykładów, testując je w swoim środowisku, poprawiając błędy i dopracowując je, aż w końcu opanują język. To nie jest złe podejście; sprawdza się w przypadku większości języków programowania. Prawdę mówiąc, sprawdza się w większości zagadnień. Przecież tak właśnie dzieci uczą się mówić! Żadnej teorii - tylko mnóstwo przykładów i mnóstwo błędów.
Jednak DAX bardziej przypomina matematykę, statystykę i fizykę. Nie da się go nauczyć po prostu metodą prób i błędów. Trzeba go poznawać tak samo, jak uczymy się jakiegoś matematycznego zagadnienia: trzeba je przestudiować. Do następnego kroku możemy przejść dopiero wtedy, gdy poprzedni jest zrozumiały i ugruntowany.
Podsumowanie
Czas na kilka wniosków, dlaczego DAX jest prosty, ale nie jest łatwy. Wszystko sprowadza się do tego, jak podejdziesz do procesu nauki. DAX jest prosty, przez co należy rozumieć, że jego podstawy nie są nadmiernie złożone. Wręcz przeciwnie, są proste i jest ich tylko kilka. Jednak trzeba wielu powtarzalnych ćwiczeń, zanim je naprawdę zrozumiesz i opanujesz. Kiedy to się uda, okaże się, że DAX jest również łatwy. Jeśli nie, albo gdy zbyt pośpiesznie będziesz dążyć do swojego celu, nauka DAX stanie się prawdziwym wyzwaniem.
Gdybyśmy mieli dać najprostszą radę dowolnemu nowemu użytkownikowi DAX, brzmiałaby ona: Ćwicz podstawy. Jest to ta sama rada, którą ambitnemu młodemu sportowcowi da każdy doświadczony trener.
Ćwicz
Od
Podstaw
Nie rezygnuj, gdy cokolwiek (być może wszystko!) wydaje się niejasne. Wróć do podstaw i przestudiuj je jeszcze raz. Wyniki pojawią się same. Wiemy, bo to się sprawdziło w naszym przypadku. Zadziała i w twoim.
1 Stwierdzenie to jest prawdziwe jedynie w przypadku angielskiej (niezlokalizowanej) wersji programu Excel. W polskiej wersji interfejsu użytkownika formuła dla Excela przybierze postać JEŻELI([@SalesAmount]>10; 1; 0). Warto zauważyć, że nie tylko przetłumaczone zostało słowo kluczowe IF, ale dodatkowo znak separatora zmienił się z przecinka na średnik. To ostatnie zachowanie, czyli zamianę przecinka na średnik, dostrzeżemy również w Power Pivot w polskiej wersji Excela, choć nazwy funkcji pozostaną niezmienione (wszystkie przypisy pochodzą od tłumacza).