從SUM讓人看看PowerBI DAX的坑爹之深

  • 2019 年 10 月 6 日
  • 筆記

DAX,simple but not easy.DAX,簡約而不簡單

如果說99%的人不真正理解PowerBI DAX的SUM,你信嗎?只怕是說少了,從這個意義上講,PowerBI就是一個坑爹的。

我們敢說自己在深入的研究PowerBI,但絕不敢說我們理解了PowerBI的一切,本文讓你看看它坑爹的深度。

現給出結論:在DAX中不存在真正的SUM。很多人一定懵了,DAX 在被設計的時候被刻意地與Excel函數的用法做了貼近,而導致PowerBI的運算可以很快入門,但很快就會遇到一個普遍現象:DAX的計算結果和你期望的結果完全不同,而你無法解釋為什麼。

SUM的迷思

從一道經典的問題來看:

用戶在訂單表創建了一個計算列,並寫入一個簡單的公式,在按下回車的時候,每行的值應該是多少?請思考10秒繼續。 有兩個選擇: A 每行都是一樣的數字,為整個列值的和。 B 每行的數字都是當前行的銷售額。

這個糾結非常符合入門夥伴會犯的錯誤。大家在學習了行上下文以後,了解到創建計算列的時候會創建行上下文,因此,SUM( FactOrders[銷售額] ) 發生在當前的行上下文中,而由於行上下文的存在,FactOrders[銷售額] 應該是當前行的值,所以,SUM( FactOrders[銷售額] ) 應該和當前行的值一樣。這個理解從邏輯上和已經學習的規則上是完全正確的。而你立馬會被顛覆三觀地看到:

你毫無辦法,DAX就是這麼任性,怎麼辦怎麼辦,打開《DAX聖經》來尋找答案吧:

在這一章節是這樣描述的:

其含義是:這裡雖然有行上下文,但 SUM 會忽略行上下文。也許,是SQLBI老師為了讓大家簡化理解,所以這樣描述了結果。在我第一次學習到這裡的時候,腦中有四個疑問: 1、剛學完行上下文規則就出現破壞規則的特例,鬱悶!好吧!那麼除了SUM,還有誰會忽略? 2、如果剛學完的規則馬上就被破壞,那規則還有什麼用,難道DAX就是這樣隨意的設計嗎? 3、憑什麼你說忽略就忽略啊,有什麼證據啊? 4、這個疑問最嚴重,直接導致我可以把書撕了,不是剛才剛說好DAX有兩個上下文,一個行上下文,一個篩選上下文,所有的運算都是發生在這兩種上下文中,無一例外。(好棒,這個規則太好了),暈,這麼快就來一個可以忽略行上下文的東西。那剛剛說的算什麼~~~ 非常氣憤!!!

如果你第一次學習DAX會有類似的疑問,那您和我是一類人,崇尚完美,不喜歡破壞規則,而也充分相信DAX的設計者不會把DAX設計成一個這麼傻的東西。在很多學員中,都記住了這個特殊規則,但這裡想告訴你的是:DAX有兩個上下文,一個行上下文,一個篩選上下文,所有的運算都是發生在這兩種上下文中,無一例外。既然無一例外,那麼SUM會不會忽略行上下文呢?當然不會了,那怎麼解釋這個問題呢?真正的原因在於:在DAX中,是不存在SUM的,任何的SUM在計算時都會轉化成SUMX。

也就是說:SUM(T[C])會被無一例外的轉化為SUMX( T , T[C] ),因此在DAX實際執行的時候,是沒有SUM的。

回到這個問題來看,這裡確實不會忽略行上下文,而是將SUM轉換成了SUMX,並進入SUMX進行計算,而SUMX將創建新的迭代過程,在表中逐行迭代,進而得到了整列的聚合結果。這個解釋完美的解釋了這裡的問題,同時沒有引入特例,沒有不完美,非常好。

你是想問 SQLBI 的大師是不是說錯了呢?嚴格意義上說,當然是了。他們引入了特例且用這種生硬的辦法來定義規則,不可接受。但我也充分知道他們和我一樣是知道SUM會被轉換為SUMX的,他們這樣描述,我猜想是要避免告訴你這麼多底層的內容,就會增加學習難度。

吾愛吾師,吾更愛真理。並不是一個口號,它體現在你所有的學習和工作中。注意,我們給出這個案例絕對不是為了證明義大利人錯了,而我們更牛,非也,這毫無意義。我們給出了一種理解和思考問題的方式以及真實的一個學習過程。正相反,我們無數次的研究了義大利人寫書的邏輯和順序,可以發現其編排是精心考慮的,我們能快速順利的接受DAX,必須承認他們做了巨大的貢獻,這裡僅僅是一點點腳註來輔助當時和我們有一樣困惑的讀者。

更複雜的迷思

來看一個更複雜的迷思來挑戰你對DAX的認知吧:如何計算積累銷售額,其演算法是這樣的:

其效果如下:

從效果上看,很容易理解,這個是積累求和的典型案例。但如果你用自己學的DAX知識來解釋的時候,就會發現發現問題了,我們一起來看:

第8行,在FILTER 第9行,ALL(DimDates[Date])得到了所有的日期,由於FILTER是迭代函數,進行迭代並逐一創建行上下文。 第10行,在創建的行上下文中來比較 DimDates[Date] 與 MAX ( DimDates[Date] ),問題來了,既然是在行上下文中,DimDates[Date] 只有一個值,那麼 MAX ( DimDates[Date] ) 將永遠等於 DimDates[Date] 本身,那麼也就無法篩選到小於當前日期的所有日期。

對於這個問題,我們再看看義大利人曾經的解釋是怎樣的:

在讀到這裡的時候,我們首先想到的應該是什麼,是不變的規則:DAX有兩個上下文,一個行上下文,一個篩選上下文,所有的運算都是發生在這兩種上下文中,無一例外。

按照義大利老師的解釋,MAX直接運行在了篩選上下文中,我的天,那行上下文呢?至少在SUM的章節還提了SUM會忽略行上下文,到這裡乾脆連提都不提了,因此,在SUM的那個章節如果你沒有真正搞懂的話,到這裡說自己懂的,怎麼可能呢~~~ 你此時可以有和SUM一樣的理解,MAX和SUM一樣忽略了行上下文,所以只有篩選上下文了。而真實的情況是:DAX中沒有真正的MAX,所有的MAX在實際都會轉換為MAXX,也就是MAX(T[C])的本質是MAXX(T,T[C]),因此在第10行到第11行,MAX ( DimDates[Date] ) = MAXX ( DimDates , DimDates[Date] ) ,此時可以繼續解讀MAXX中的DimDates,而它所處的篩選上下文,正是當月,所以MAXX ( DimDates , DimDates[Date] ) 會得到當月的最後一天。

至此,就搞清楚了這個積累求和模式。目前搞清楚這個積累求和模式的人還非常少。

如果你認為你自己是其中一個位,那麼請繼續看。

更更複雜的迷思

注意:在積累求和模式中,第9行,這裡是 ALL ( DimDates[Date] ),而外部的篩選環境是 DimDates[Month],因此,ALL ( DimDates[Date] ) 雖然可以忽略對 [Date] 的篩選,但卻不能忽略對 DimDates[Month] 的篩選,那麼,在任何一個月,都只會在當月迭代,是無法出現從歷史至今的效果的,一個非常巨大的矛盾出現了。

怎麼解?這個問題已經超過了這裡討論的範疇。直接可以給出結論是:由於 DimDates 是日期表,系統會在 CALCULATE 的計算中自動加入 ALL( DimDates )。就不再展開了。只有極少數奇葩分子會研究下去,當然,他們也將是最終掌握DAX的人。

總結

記得在大學學習過C語言,然後有一個奇葩面試題是:在C語言中,有:a++; ++ a ; a = a + 1 ; a = a += 1,請問哪個執行效率高? C語言經過了編譯器才轉換為二進位程式碼得到執行;在DAX中,類似的是DAX表達式會被轉換成對DAX引擎的查詢計劃才會被執行。 PowerBI的學習者有三類: 1、拖拽可視化的; 2、解決實際問題就行,搞不清楚就記住; 3、追根究底的。

我們並不是說類型3才是正確的選擇,因為每個人的定位是不同的,不過,在Excel120,我們覆蓋從1到3的全部內容,歡迎大家交流探討,共同進步。 但是通過這些內容,我們可以知道的是:DAX,simple but not easy。我們在Excel120窮舉式的研究PowerBI的所有細節。 學習DAX的階段是: 1、看了能懂,寫不出來 2、基礎的寫的出來,複雜的寫不出來 3、複雜的寫出來,但有的坎過不去 4、理解所有細節,性能調優

PowerBI的坑爹就是越坑越著迷越上癮,真正學會了 DAX,PowerBI就是你的玩具,祝大家玩得開心。

為了徹底攻克Power體系,讓更多的人拿來就用,我們正在考慮建立《PowerBI DAX 數據建模設計思想大全》,預覽:

感興趣的歡迎交流