大多書上沒說明白的DAX計算核心原理與計算上下文

DAX在使用上,分成兩大類,一類以DAX計算為核心,另一類以DAX查詢為核心。

本文在於揭示DAX計算背後的原理。DAX查詢後續單開再議。

本文需要對DAX略有了解,你可能正模糊地理解著行上下文以及篩選上下文,那本文正適合來幫你理清這兩個非常重要的DAX特性。更關鍵的是,本文將直接揭示DAX計算的原理。

關於DAX計算,會表現為你在編寫 計算列度量值 時直接應用,也是建立對CALCULATE正確思維模式的基礎。

關於上下文(context),其含義為:周圍環境。也就是說,要確定某種含義,必須考慮到它所處於的環境中,上下文正是這種周圍環境。

DAX的計算也一定在某種環境中,那就是計算所處的計算上下文(Evaluation Context),具體而言分為:行上下文(Row Context)篩選上下文(Filter Context)

理解DAX的計算原理

DAX計算就其本質,可以理解為在建立了關係的多個表構成的數據模型上,DAX通過篩選,找到需要進行計算的一個數據模型的子集,然後完成聚合型計算。下面詳細解釋。

如果你剛從Excel過來,那必須要了解:DAX包含了一些和Excel函數類似的函數及用法,但DAX更多地提供了專有的函數和用法。

最重要的是DAX在計算上的原理和Excel是完全不同的,這也是Excel用戶剛剛接觸DAX後最大的問題所在,那就是帶著Excel函數的思維模式來套DAX。

需要強調的是:如果說Excel函數的計算是基於單元格的,那麼DAX的計算是基於 進行的。

DAX基於列運算,也就保證其能大規模地處理數據。想像一下,一個日期列有一萬個日期,如果是基於單元格計算,那要計算一萬次,而基於列計算,也許只要1次。DAX強大的根本也正在於此。

那麼問題來了:DAX如何完成對特定行單元格的計算,或者說DAX如何在特定的行進行計算。

也就是說:

DAX 具備粗獷式地按列運算,這必須做到,確保DAX可以完成大規模計算。

DAX 具備細膩式地按行運算,也必須做到,確保DAX可以完成精確地計算。

DAX在設計上,必須兼顧這兩種目標。DAX提供了兩種關鍵特性:一種是篩選,一種是迭代

DAX篩選,就是按照一定規則(篩選規則,如切片器,Filter等)選擇出模型的一個子集。

DAX迭代,就是進一步針對這個子集的每一行進行輪詢處理(如:2017年的訂單表每行的單價乘以數量便得到銷售額)。

DAX計算,則直接完成某種聚合運算(如:SUM剛剛的每一行得到總銷售額)。

重要的事情值得再次強調:

DAX計算(往往就是CALCULATE),在本質上就是篩選出數據模型的子集,然後對進行迭代處理後進行聚合運算。(其中的各個步驟可能省略或跳過)

注意:如果被篩選得只有一行一列,也就是出現行列交叉處,即單元格。也就完成了對單元格的處理。

篩選上下文,正是完成篩選的關鍵機制,而行上下文,正是完成迭代中行中計算的關鍵機制。

理解行上下文

【第一種情況】DAX表達式對列進行操作,通常會寫下如下的表達式:

Gross Profit := SUMX ( Sales, Sales[Amount] – Sales[TotalCost] )

在上述表達式中,Sales [Amount]和Sales [TotalCost]是列引用。即:用列的名稱表示列值,在示例中,可以獲取SUMX當前迭代行的Sales [Amount]值。

【第二種情況】DAX使用列引用來制定某些功能對列執行操作,如以下計算產品名稱數量的度量:

NumOfAllProducts := COUNTROWS ( VALUES ( Product[ProductName] ) )

在這種情況下,Product [ProductName]不檢索特定行的ProductName值。相反,使用列引用來告訴VALUES要使用的列本身。換句話說,你引用列,而不是它的每個行值。

綜合上述兩種情況,可以發現 列引用 是一個有點模糊的定義,因為不管是引用了特定行中的列值,還是引用了完整的列本身,都在用相同的語法,這就很難從意思上區分開到底是在用哪一種。然而也正是這種模糊為DAX表達式帶來好處,就是易於閱讀(流暢地閱讀);但我們必須時刻清楚:現在使用的是列值還是列本身(列引用)。

因此,作為DAX編寫者,你必須一邊編寫DAX,一邊同時思考,當前正在使用DAX列引用,還是列引用下的每個值。如果將這種思維變成了自然的感覺也就進入了DAX新的階段。

這個區分就在於:DAX計算使用的列引用是不是位於一個行上下文中

當您使用列引用來檢索給定行中的列的值時,需要一種方法來告訴DAX要使用的行,用於計算該值。換句話說,您需要一種方法來定義表的當前行。這個概念就是「當前行」的行上下文。

無論是顯式地(使用迭代器)還是隱式地(在計算的列中)遍歷表,都是在使用行上下文:

  • 計算列中編寫表達式時,將為每一行計算表達式,為每行創建行上下文。

  • 當使用像FILTER,SUMX,AVERAGEX,ADDCOLUMNS等屬於迭代器函數的任何一個DAX函數。

如果行上下文不可用,試圖直接對列引用進行計算就會產生錯誤。例如:在DAX度量值中僅寫入列引用,就會導致這種錯誤,因為行上下文不存在。如下所示的方法無效:

SalesAmount := Sales[Amount]

為了使其工作,您可以對該列進行聚合運算。事實上,SalesAmount的正確定義也應該是:

SalesAmount := SUM ( Sales[Amount] )

SUM的本質是SUMX的變體寫法,SUMX就屬於迭代器函數,將遍歷Sales[Amount] 列的每行,並通過行上下文,提出當前的行值(由於位於行列交叉,也就是單元格值),然後進行聚合累加運算而得到最後結果。

迭代器函數可以完成更複雜的計算,就是嵌套起來使用。例如,可以寫:

AverageDiscountedSalesPerCustomer := AVERAGEX ( Customer, SUMX ( RELATEDTABLE ( Sales ), Sales[SalesAmount] * Customer[DiscountPct] ))

在最內層的表達式中,引用Sales [SalesAmount]和Customer [DiscountPct],即來自不同表的兩列。可以順利正確地執行操作。這裡有兩個迭代器,分別產生行上下文:AVERAGEX在Customer表進行迭代並引入第一套上下文,SUMX在Sales表上進行迭代並引入另一套行上下文。此外,值得注意的是,RELATEDTABLE相關行還使用行上下文來確定要返回的相關行集合。事實上,RELATEDTABLE(Sales)返回當前客戶的銷售記錄,其中「當前」是指AVERAGEX當前迭代的客戶。

篩選上下文

篩選上下文是在DAX表達式在計算開始之前就應用於數據模型的一組篩選器。例如,在數據透視表中使用度量值時,它會為每個單元格產生不同的結果,這是因為相同的表達式在將基於數據模型的不同子集進行計算。微軟將數據透視表的用戶界面應用的篩選器以及可以在度量值中寫入的DAX表達式應用的其他篩選器統稱為查詢上下文。實際上,這些篩選器的效果幾乎是相同的(實際差異對於這裡來說並不重要),因此我們簡單地將篩選上下文定義為限制DAX表達式計算(通常是度量值)的一組篩選器,不管它們是如何生成的。

例如,下圖中突出顯示的單元格具有2007年的篩選器上下文,顏色等於Black,產品品牌等於Contoso。這就是為什麼它的度量值不同的原因。

可以通過使用CALCULATE得到相同的計算效果。例如,以下DAX查詢中進行的DAX計算正返回上一圖片中突出顯示的單元格的相同值。

DAX查詢不是本文的主題,可以暫時忽略對它的理解。這裡為了揭示透視表的界面操作和DAX計算時設置篩選器可以達到相同的效果,是等價的。

EVALUATEROW ( "Sales Amount", CALCULATE ( [Sales Amount], Date[Calendar Year] = "CY 2007", Product[Color] = "Black", Product[Brand] = "Contoso" ))

通常,在透視表的每個單元格都有不同的篩選上下文,這是由用戶界面(如Excel中的數據透視表)隱式定義的,也可以使用CALCULATE顯式地通過某些DAX表達式進行定義。

應用於Excel中的數據透視表或Power BI Desktop或Power View的任何用戶界面元素都會作為篩選器影響篩選上下文,但這並不直接影響行上下文。

篩選上下文實際是數據模型上的一組篩選器。任何DAX表達式始終都在篩選上下文中執行。如果篩選上下文為空,DAX表達式在整個數據模型中運行。當篩選上下文不為空時,它限制DAX表達式在被篩選後的數據模型中運行。

最後,值得注意的是可以把篩選上下文理解為一組等效的篩選器(事實也正是如此)。所在,在提到篩選上下文的時候,其實是一組篩選器。

理解篩選的傳播

行上下文不會通過關係傳播。如果表中有行上下文,則可以使用RELATEDTABLE在關係的多方迭代表的行,也可以使用RELATED訪問父表(關係一方)的行。

應用於表列的篩選器會影響該表的所有行,以過濾滿足該篩選器的行。如果將兩個或多個篩選器應用於同一個表列,則將它們視為邏輯與條件,只有滿足所有篩選器的行才能保留在該篩選上下文中繼續由DAX表達式計算。

根據關係的方向,篩選上下文會通過關係從關係一方自動傳遞到關係多方。

在Excel的Power Pivot中,這樣一個方向只有一對多,所以在一個關係的一邊應用的篩選器會影響關係多方的錶行,但是篩選器不會按相反的方向傳遞。例如,考慮一個模型,其中有兩個表,Product和Customer,每個表與Sales表具有一對多關係。通過篩選器過濾產品也就相當於過濾了(篩選的傳遞)產品的銷售,但不能過濾(篩選不會繼續傳遞)購買這些產品的客戶。

在Power BI Desktop中,可以啟用雙向交叉篩選。通過啟用雙向交叉篩選器,一旦過濾了一個表,也就自動過濾了關係鏈上的所有表。例如,當過濾產品上的行時,會隱式地過濾Sales和Customer,以這種方式過濾出產品相關聯的客戶。

總結

  • DAX計算的本質是基於篩選出數據模型的一個子集進行迭代後進行聚合型計算(可以忽略或跳過其中環節)。

  • DAX計算的完成有賴於篩選上下文和行上下文,它們統稱為計算上下文。

  • 編寫DAX表達式時,通過控制行上下文和篩選上下文已達到預期的運算。

  • 行上下文不會通過關係自動傳遞,而篩選上下文將獨立於DAX代碼遍歷關係併產生篩選傳遞的效果。

  • 可以使用DAX函數(如CALCULATE,CALCULATETABLE,ALL,VALUES,FILTER,USERELATIONSHIP和CROSSFILTER)來控制篩選上下文及其傳遞,這些在後續的篇章中繼續說明。

本文由Excel120博主佐羅整理髮布。


推薦閱讀:

PowerQuery的優勢與短板
DAX學習分享:十條經驗
Power BI 即將放出的黑科技—Microsoft Data Insight Summit精華回顧
使用Power Query自定義生成日期表

TAG:PowerBI | MicrosoftExcel |