CALCULATE之庖丁解牛系列 -- CALCULATE專解(3)? ? ? ?
? ? ? 把一些東西寫出來,其實是蠻需要時間的。有時候可能是出于愛好、有時候是因為能幫助自己靜下心來。
? ? ? 該系列不承諾全部內容連載完整以及即時的錯誤修訂,只是基于對DAX的熱愛而不定期更新。本人聲明:不對你由此系列造成的理解、操作錯誤等承擔任何責任。本系列最后有關于DAX三板斧(三步曲)的第一次結論性論述(這是用于DAX邏輯書寫的通用性歸納。但必須有前面的輔墊。事先申明,DAX三步曲為Power BI非官方獨家專有詞)。
? ? ? ? DAX具有很大的抽象性,也具有本身的一些邏輯規律和特點。但所有關鍵性的問題幾乎都涉及到CALCULATE。它是整個BI業務邏輯建模基礎和核心(企業級BI同樣需要)。想要理解它,需要時間和實踐,學過IT代碼的也不會是你成為DAX專家的理由或資本。
? ? ? 因為“DAX語言”的目標是“全民BI”,偏重于解決業務邏輯層面,并以構建自由式業務數據模型為主。
? ? ? 所有這些,都在提示我們,應該以一種學習一門語言的方式來研究它。很多人很在乎一些DAX的內部運行、很迷戀官方“教科書”的措辭或概念。
? ? ? 當然,我們絕不是、也不能說官方的是錯的(相反,參考的就是它們)。我們只是想以自己更能理解的簡體漢文的方式來認識它(比如讀不準某個英文發音時,我們可以使用自己的方言注釋一下,理解其意思并能利用它,誰會關心你理解它的方式?)。
? ? ? 所以我們說,學習DAX,你可以以你理解的任何方式了解它。當你的了解不能解釋更多的問題時,你需要增加你的詞匯量以便能更好的理解并與之溝通??傆腥苏f,我已好久沒用過它了,幾乎全忘了。這其實是很有問題的說法,既然DAX是一種語言,哪有那么容易忘記的?就像是我們學會了英語,幾天不說,難道就全忘了?
? ? ? 走題了,回到主題。上部分我們主要談到,要以列表的思維方式來理DAX(CALCULATE)的行為。并強調了列表的兩種存在形態:值列表與列表,以及對應的值列表(隱式行)篩選與顯式列表篩選。也提及到還沒有詳細討論的列表基數(唯一值)。
? ? ? 而且,我們說,在數據模型里其實是沒有維度的(即沒有事實表與查找表之分),只有單列表或具有列基數關系的單列表,它們構成數據模型以及篩選+計算......。
? ? ? 所有這些要了解的內容,都是一些能跟DAX溝通的語言方式,雖然還并不流利,還不完整,但那只是時間與實踐的問題。
? ? ? 至少目前,我們試圖通過一些簡單的理解,來解釋一大堆DAX中遇到的困惑問題。但在我們需要寫出比元度量更復雜一些的公式前,還需要了解關于值列表和列表的一些基礎。
? ? 第10式:CALCULATE的列表數據類型與列值格式
? ? ? 我們知道,DAX可以對不同的數值類型進行計算,其中包括:整數、十進制數、貨幣(其實際還是整數,內部以整數存儲)、布爾值 (TRUE/FALSE,真/假)、文本、二進制大對象等7種。
? ? ? 你可以分別設置列表的數據類型(如果對某列中的值的類型有要求,還可以設置需要的列值的格式)。
? ? ? DAX擁有一個強大的類型處理系統,因此不必過多擔心數據類型。
? ? ? 在DAX術語中,布爾值被稱為TRUE/FALSE,其實際是值列表(將某列的值定義為:符合條件的與不符合條件的兩類列值),我們傾向于遵循事實上的命名標準,將它們稱為布爾值。
? ? ? ? 注意:布爾數據類型常用于表示邏輯條件。而且,由于它將整個列基數分成事實上的兩部分(被TRUE / FLASE分割)。因而,布爾邏輯條件的內部執行是相當快的。這將是DAX優化的一種可能選擇。
? ? ? 例如,由以下表達式定義的計算列是布爾型:
? ? ? = Sales[Unit Price] > Sales[Unit Cost]
? ? ? 你可以看到,該布爾型數據類型也可以是:TRUE= 1和FALSE = 0的數字。額外的,這對于排序的時候可能也有用,因為TRUE> FALSE(我們經常會計算那些顯式出現在透視表里的行:比如當前有銷售的商品,你可以將它定義為TRUE= 1,而那些當前沒有出現的無銷售商品則定義為FALSE = 0,它可能也是你需要計算的部分)。
? ? ? ? DAX中一個比較需要注意的是值列表形式: BLANK() --空值或缺失值
? ? ? ? 在DAX公式中,BLANK() 代表沒有任何值、缺失或空的值。我們可以通過BLANK() 函數(不含參數)以獲得DAX的該值。這樣的值只能作為DAX表達式的結果,不能在任何比較語句中使用它(請參見ISBLANK)。
? ? ? 在數字表達式中,BLANK()被自動轉換為0,而在字符串表達式中,BLANK()將被自動轉換為空字符串—在表達式結果中保留空白的某些異常(如產品、除法的分子或空格的總和等)。? ? ? ?
? ? ? ? 在今后的示例中,你將看到DAX在不同的操作中如何處理數字、字符串和布爾數據類型的BLANK()。通常,我們使用BLANK()作為表達式的結果。
? ? ? ? 注意:一個DAX表達式,其結果的類型是基于表達式中使用的公式所定義的類型。
? ? ? 需要意識到這一點,是因為你需要防止:從DAX表達式返回的類型可能不是預期的類型。那么,你必須重新檢查表達式本身中公式指代的數據類型。
? ? ? 如果返回的類型不是你需要的,比如文本格式的數字,例如以一個或多個0開頭的文本格式,結果可能是不為零的整數數字,這是內部引擎依據公式定義的類別,而不是元列表類別,你需要在公式中定義其類型。
? ? ? ? 再例如,如果一個求和項是一個日期,那么,其結果也會是一個日期。然而,如果它同一個整數運算符一起使用(時期類型將被引擎內部隱式處理為整數類型),則結果將是一個整數。
? ? ? 這就是“操作符重載”,可以看到圖中的行為示例。
? ? ? 如下圖,[時期_7天]列,是通過在[發貨時期]列的值中+7來計算的。結果為一個日期類型的列(你定義的是時期類別計算)。 也就是說,將一個整數添加到日期結果中,日期增加了相應的天數(往后 “+”,往前“-”)。
? ? ? 當然,你使用“操作符重載”還可以處理某些特別的字符結果需要。除了“運算符重載”之外,當需要操作符時,DAX會自動將字符串和數字都轉換成數字。例如,如果使用& 運算符,將字符串串聯起來,則DAX將其參數轉換為字符串。你看這個公式:
? ? ? ? = 5&4? ? ? ? -- 返回字符串形式“54”;
? ? ? ? = "5" + "4" -- 返回值為9的整數結果。
? ? ? 這些產生的值將依賴于操作符定義轉換,而不是數據源的列。即使這看起來很方便,但在這些自動轉換過程中將會發生什么類型的錯誤,不得而知。
? ? ? 因此,我們建議你避免自動轉換,強調該規則是幫助你理解類型轉換的特點。如果確實需要進行某種轉換,那么,對其進行控制并將其轉換成顯式化處理,則會更好。為了更加明確,前面的例子:= "5" + "4" ,應該使用值轉換函數來定義它:
? ? ? ? ? ? ? = VALUE("5") + VALUE("4")
? ? ? 對于習慣于Excel或其他語言的人來說,DAX數據類型可能是熟悉的。你可以專門就這一部分,去官方了解DAX數據類型的規范。但是,對于每種數據類型,使用通用的方式處理也是很有用的。例如,任何一個計算,只要你定義的列表是數值類型,則結果一定是數值類型列表。
? ? ? ? 因此,我們將剛剛示例的:一個整數添加到一個時期(時間)值時,該值會增加相應的天數。但是,你可能會習慣于使用專用的轉換函數:FORMAT函數從日期列里提取日、月和年等,這會更方便。
? ? ? ? Day=DAY( Calendar[Date] )
? ? ? ? Month=FORMAT(Calendar[Date],"mmmm")
? ? ? ? Year=YEAR(Calendar[Date] )
? ? ? 你需要知道,通常列表類型轉換是自動發生的,并且一般不需要你在表達式中調用轉換函數。即使在某些情況下,可能你有明確的意圖,希望這樣做以強制某個特定的行為或使我們的語句更易于閱讀。
? ? ? 貨幣數據類型是一個定點小數,這在財務計算中非常有用。而datetime數據類型使用浮點數存儲該值,其中整數對應于天數,例如:
? ? ? ? =NOW()+0.5 (如果你在公式里定義該條件,+0.5的形式是告訴DAX,你是將某個值列表的值+0.5,無論原NOW()類型什么,引擎總是以數值來處理它,很簡單,因為只有值列表才能加減乘除計算)
? ? ? 如果我們需要一個將業務時間延長12個小時(這正好是半天(表示為+0.5),也可以換算成其他多少小時對應的天數)。但是,你應該考慮使用特定的DAX函數,比如 DATEADD,以便使代碼更具可讀性。如果只需要DATETIME的日期部分,請始終記?。菏褂肨RUNC來去掉小數部分(而不是乘以某個適當的整數值)。
? ? ? ? 之外,由于列類型由最后的計算定義的結果決定,當結果類型錯誤時,你需要仔細檢查你是否有改變元列表類型的定義行為。如圖: 我們定義某個度量值列表的2.5% 倍計算,然而這種表示會讓DAX迷糊,從而出現提示錯誤:無效的標記。
? ? ? 這應該不是DAX的BUG,如果將其作為BUG修復,估計非改動引擎不可。而我們只需要修改2.5%為0.025即可。原因很簡單,DAX認為你定義了兩種類別給它:一個是作為值的2.5,一個是作為文本字符串的“%”符號。雖然這個案例有些特別,也是一個不大不少的美麗的誤會,但對于你了解列表類型,發現問題的原因有幫助。因為在今后的實踐中,你還會遇到各種問題。
? ? 第11式:CALCULATE列表的邏輯操作符
? ? ? 與列表數據類型相關的另一個概念是:DAX(CALCULATE)的邏輯操作符。因為操作符在確定表達式類型時具有決定性:DAX表達式的結果類型,是基于公式定義的類型。
? ? ? ? 下圖,顯示了在DAX語言中可用的操作符列表:
? ? ? 直至目前為止,你可以將凡是使用操作符構建的列表單元,都理解為CaIcuⅠate的布爾值邏輯條件(或值列表條件),重要的還是老生常談的那句話:結果總是列表。此外,為了兼容Excel語法,邏輯運算符也有相應的DAX函數可用。例如,你可以編寫表示“或”以及“并”關系的操作符:
? ? ? 在DAX表達式中,像這種使用“中國”或“0”這樣的標量值,或者,引用列表中的行值,它們都是列值。當我們引用一個列以獲取其列值時,我們已經知道可使用以下基本語法:
? ? ? '表的名稱'[列名稱]
? ? ? 你發現,這都是在單列表上的操作。
? ? ? 如果需要操作列表的多個列值的邏輯運算符,除了OR之外,還提供了支持IN函數的邏輯運算符,DAX引入的這兩個新函數的語法:它對應列表和值列表構造函數(OR或IN),這使得我們可以創建“虛擬”表來比較兩個或兩個以上的不同列值,而不僅僅是一個單獨的列值(基數)。
? ? ? 例如:
CALCULATE ([銷售額],
商品信息[顏色] ="紅色" || 商品信息[顏色] = "黑色")? -- “或”的關系
? ? ? 有了IN的語法,你可以這樣寫:
CALCULATE ([銷售額],
商品信息[顏色] IN ("紅色" ," "黑色")
? ? ? 正如公式所示,在度量表達式中使用IN操作符,可以比較一個或多個列值(相當于多個列,因為每個布爾值范圍的列值將構成新的值列表)。前面的例子中,在操作符后面的語法是一個列表構造函數,當它的內容有多個列值時,都可以有一個行值構造函數(值列表)。
? ? ? IN運算符實際是一個CONTAINSROW函數,上述IN公式還可以寫成以CONTAINSROW定義的邏輯運算符:
? ? ? ? CALCULATE ([銷售額],
? ? ? ? CONTAINSROW({"紅色","黑色"},
? ? ? ? 商品信息[顏色]))
? ? ? 關于邏輯操作符后面還將討論,這里從略。
? ? ? 值得指出的是,當需要編寫復雜的多個邏輯條件時,使用函數方式而不是操作符方式,來處理布爾邏輯將變得非常有用。事實上,后面的列表篩選還將介紹。你可以將單個值列表篩選看成是單個不同的布爾值邏輯。
? ? ? 實際上,在格式化比較長的代碼段時,函數方式比操作符更容易格式化和讀取。但是,函數的一個主要缺點是,只能一次傳遞兩個參數。如果計算需要兩個以上的條件時,需要函數嵌套。你可以根據實際決定何種方式。
? ? 條件語句(IF、SWITH)
? ? ? 由于DAX是一種函數式語言,經典的IF條件語句也可用于DAX公式。
? ? ? 第一個也是最常用的條件語句是IF,它有三個參數:第一個是測試條件,第二個是如果第一個參數計算為TRUE時返回的值,第三個是最后要返回的值。如果省略第三個參數,則默認為空。例如:
? ? ? ? IF ( 20 <15, "second", BLANK() ) = BLANK()
? ? ? 1)我們可以使用嵌套IF語句檢查表達式的不同值(實際也可理解為:布爾值邏輯)。但是,這種情況,我們可以通過使用SWITCH函數獲得更多可讀的代碼,以代替嵌套的IF語句。在數據模型內部,它生成的代碼與嵌套的IF語句完全相同,而且性能也是相同的。
? ? ? ? 需要注意的是,SWITCH函數的第一參數有兩種形式:
? ? ? ? SWITCH(銷售表[銷售] ......,第一種為函數引用的某個單列表。
? ? ? ? 2)我們還可以使用SWITCH來測試不同的、不相關的條件,而不只是匹配由表達式返回的單個值。這可以通過將TRUE()作為SWITCH的第一個參數來獲得,接著再編寫其要測試的每個條件的邏輯表達式。例如,我們可以編寫下面的SWITCH語句:
? ? ? ? SWITCH(TRUE(), ......),? ?
? ? ? ? 例如:我們將商品信息列中的商品,按價格高低(來自[價格]列),設定為四擋:
? ? ? SWITCH(TRUE(),
? ? ? ? 商品信息[價格]<10,"低檔",
? ? ? ? 商品信息[價格]<50,"中檔",
? ? ? ? 商品信息[價格]<100,"高檔","精品")
注意:SWⅠCTH函數無論哪種形式,其實際是針對一列的全部列值的"值列表"分割。比如前面的[價格]<50為中檔,表達的列值是10<中檔<50這個范圍,因為前面的<10已將所有該范圍的列值從列中分割。所以,接下來的<50,并不需要“and"的(<50,并且>10),因為這時候的列值已沒有<10的列值,以此類推。這是寫SWlCTH函數需要注意的。
? ? ? ? 你可以使用ISBLANK函數檢查表達式是否為空。一個常見用法是為表達式定義一個默認值,以防止其計算結果為空值,如下例所示:
? ? ? ? IF(ISBLANK(Sales[數量]),1, Sales[數量] ) --"1"為指定返回的值(或其他值替代"1")。
? ? ? 你還可以使用ISERROR函數檢測表達式中的錯誤。例如,如果想避免在語句中傳遞錯誤值,你可以編寫這樣的表達式:
? ? ? IF(ISERROR(SQRT(Test[Omega])), BLANK(), SQRT(Test[Omega]) )
? ? ? 當實現前面的模式,在出現錯誤時返回一個值時,可以使用ISERROR避免重復相同的表達式,如果它產生一個錯誤,會自動返回作為第一個參數傳遞的表達式:
? ? ? ? IFERROR(SQRT(Test[Omega]),BLANK() )
? ? ? ? 請記住,應盡量避免ISERROR和IFERROR的使用。預先處理錯誤總不失為更好的選擇。例如,如果前面例子中對SQRT--平方根的關注是負數的存在,那么應該使用這個表達式:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? IF(Test[Omega]>=0,SQRT(Test[Omega]),BLANK())
? ? ? ? 另外,一個常見的測試,是檢查計算比率時的分母值。在這種情況下,應該使用DIVIDE函數。所以,與其寫這個:
? ? ? ? IF(Sales[Quantity]=0,BLANK(),Sales[SalesAmount] / Sales[Quantity])
? ? ? ? 還不如采用:DIVIDE(Sales[SalesAmount], Sales[Quantity])
? ? ? 基于:盡量避免使用ISERROR和ISERROR的勸告,我們應該始終使用DIVIDE,以保護表達式不受division -by- zero錯誤的影響,因為它提供了比基于IF方法更好的性能。
? ? ? ? 接下來,則是對應于處理列表的常用函數。
? ? 第12式:CALCULATE的常用函數
? ? ? ? 既然我們將列表區分為值列表、列表兩種,并作為與CALCULATE溝通的語言方式,那么,他應該適應于到目前為止的大部分問題
? ? ? 當然,這種區分可能并不準確,只是便于學習和記憶的方式。
? ? ? 2、幾乎每個數據模型都需要對數據進行聚合操作。
? ? ? ? DAX提供了一組用來集合表中列的值并返回每個值的函數。我們稱這組函數為聚合函數。例如,下面的度量:計算Sales表銷售欄中所有數字的總和:
? ? ? ? ? Sales : = SUM( Sales[Amount] )
? ? ? 如果在計算列中使用該表達式,那么這個表達式(SUM)就會聚合表中的所有行;在一個度量中使用它,它將只考慮數據表中由切片器、行、列和篩選條件的當前行。
? ? ? 1) 一般來說,聚合函數:SUM、AVERAGE、MIN、MAX、STDEV等,只針對數值或日期類型操作。? ? ? ? ? ?
? ? ? 2) MIN和MAX是另一個比較特別的函數:如果使用這兩個參數,它們將返回兩個參數的最小值或最大值。因此,MIN(1、2)將返回1,而MAX(1、2)返回2。這個功能在2015年引入,在需要計算復雜表達式的最小值或最大值時非常有用,因為它避免在IF語句中多次寫入相同的表達式。
? ? ? ? 之前我們使用MIN和MAX的值列表特性,還可以作為是否唯一值的判斷依據。例如,使用MIN[列],用于切片器的唯一值篩選,因為當一列中只有一個值時(切片器被選的值),最小、最大值都是其本身值)。
? ? ? 3)與Excel類似,DAX為上述函數還提供了另一種語法:可以在列上進行計算,也可以同時包含數值和非數值的列值,比如文本列。該語法只是將后綴A添加到這類函數名中,從而獲得與Exce的同名函數相同的行為。
? ? ? ? 然而,這些函數只適用于包含TRUE/FLASE值的列。因為TRUE值為1,而FALSE為0,因此,無論列中的內容是什么,文本列總是被認為是0。例如,如果在文本列上使用MAXA,結果總是會得到0。此外,在執行聚合時,DAX也從不會考慮空單元格。
? ? ? ? 使這些函數可以在非數字列上使用,而不需要重新調整每個錯誤,但它們的結果是沒有用的,因為沒有自動轉換為文本列的對應數字。這些函數包括:AVERAGEA、COUNTA、MINA和MAXA。
? ? ? 4)在DAX和Excel中使用統計函數的方式則存在差異。因為在DAX中,一個列為一個類型,它的類型決定了聚合函數的類型行為。
? ? ? ? Excel能為每一個單元格處理成不同的數據類型,而DAX因為列式的特點,它為每個列處理成單個數據類型。
? ? ? ? 對于每一列,DAX都以表格式的形式處理數據,而Excel公式則處理不同類型的單元格值,沒有定義良好的類型(對類型敏感)。
? ? ? ? DAX的列式數據特點,反映在列表類型上:如果Power Pivot中的一個列存在一個數字數據類型,那么,所有的值都只能是數字或空單元格(比如你設置一個計算列,該計算式定義的列表屬性將由引擎自動填充至整列)。
? ? ? 如果一個列是文本類型(即使文本可以轉換為數字),那么,這些函數的值總被認為是空值“0”(除了COUNT)。在Excel中,值在單元格的基礎上被認為是一個數字。由于這些原因,這些DAX函數對于文本列不是很有用也不常用。
? ? ? 前面提到的聚合函數對執行值的聚合很有用。但有時,我們可能對聚合值不感興趣,而只是針對它們計數。因此,DAX提供了一組有用的函數來對行(列值)計數:
COUNT? ? ? ? ? ? ? ? ? 僅在數字列上計數:
COUNTA? ? ? ? ? ? ? ? 針對任何類型的列計數:
COUNTBLANK? ? ? 返回一個列計數的空單元格的數量:
COUNTROWS? ? ? 返回一個表中的行數:
DISTINCTCOUNT 返回一個列的不同值的數量(不重復值計數)。
? ? ? ? 其中,COUNTA 函數是唯一一個比較特殊的函數,它可以返回非空的列值,并針對任何類型的列計數。
? ? ? 另外,如果想要計算包含空值的列中的所有空值,可以使用COUNTBLANK函數。
? ? ? 對于任何表中的任何列:
? ? ? COUNTA(table[column]) +COUNTBLANK(table[column]) =
? ? ? COUNTROWS (table)。它們的結果相同。
? ? ? 特別注意,如果想要計算一個表的行數,可以使用COUNTROWS(行計數)函數。要注意,COUNTROWS需要一個表作為一個參數,而不是一個列。這是為數不多的使用頻率較大的行計數函數。
? ? ? 最后一個函數,DISTINCTCOUNT,是非常有用的一個函數,因為它:計算一個列的不同值,它作為唯一值的參數或用于求列的唯一值基數。不同的是,結果可能包含BLANK空白值。
? ? ? 當然,它其實是:COUNTROWS(DISTINCT(table[column]))這兩個函數結合的模式返回的結果,這與單獨使用DISTINCTCOUNT是相同的,盡管這更容易讀取,但DISTINCTCOUNT只需要一個函數調用。
? ? ? ? 到目前為止,我們提到的所有聚合函數都是在單列上工作的(除了COUNTROWS之外)。因此,它們可以聚合或計數來自單個列的值。
? ? ? 5)如果需要聚合表達式,而不是單個列計算,則需要一類帶X結尾的聚合函數。這組函數非常有用。特別是,當你希望使用不同的關系表的列進行計算時(后面會討論)。
? ? ? 例如,如果有一個Sales表,一個產品表的數據模型。那么,可以通過用這個表達式來定義一個度量:聚合計算每個銷售事務的總成本:
? ? ? Cost:= SUMX ( Sales, Sales[數量] *
? ? ? RELATED( 產品表[進價] ) )
? ? ? 這個度量計算了在Sales表中,每一行的銷售數量(來自Sales表)和銷售產品的標準成本--進價(來自相關的產品表)。最后,它返回所有這些計算值的和。該公式行為這里從略。
? ? ? 所有以X后綴結尾的X系列聚合函數:它們的第一個參數為一個表(整個列值),第二個參數為一個計算表達式,并返回一個由相應的該聚合函數(SUM, MIN, MAX 或 COUNT)應用于這些計算的結果。
? ? ? 這些函數有:SUMX, AVERAGEX, PRODUCTX, COUNTX, COUNTAX, CONCATENATEX, MINX,MAXX等。
? ? ? 如果再加上一些非X后綴的但具有該行為的迭代器,比如 FILTER 和 ADDCOLUMNS等,這將代表一類列表篩選的行為。為了正確理解該類列表篩選的行為,我們需要引入列表篩選的概念。所有的這些都將在稍后詳細解釋。
? ? ? 以上,我們介紹了一類主要與計算有關的函數--計算類函數,使用它們與列表的組合定義,能完成絕大多數的計算:列聚合(求和或計數)。
? ? ? 其他的計算函數,比如日期函數等。這里從略。我們有專門的函數系列專題。
? ? 第13式:CALCULATE的列表參數
? ? ? 現在,是時候見一見CALCULATE的樣子了。我們暫時只需要兩個提示:在書寫DAX公式時錄入C開頭,顯示出的第一個函數就是CALCULATE,接著提示該函數的用法與解釋。
? ? ? 我們找到官方的關于該函數的參數說明:你可以最直接的將它分為兩部分,計算式+篩選列表(值列表或列表):
? ? ? 當然,你還會記得跟著CALCULATE后面的FILTER(是的,它叫列表篩選),關于這兩個函數的講解很多(后續會討論)。但無論怎樣,最先應該了解:CALCULATE 以及 FILTER都是用于操作列表的。如是,我們最初的了解如圖:
? ? ? 最直接的理解,FILTER看起來就是CALCULATE里的其中一個FILTER參數。這里想再次提示:有關DAX的問題,都可以圍繞列表的篩選與計算來進行。至少到目前為止,它還是一個正確的結論。接下來的問題,我們需要研究CALCULATE使用列表作為參數的“語法”,因為有了溝通的基本語言方式,還需要一定的語法。
? ? ? ? 具體地說,就是列表作為CALCULATE的參數的運用,即如何定義一個DAX計算公式,特別是一些復雜公式的構成。
? ? ? 這需要學習DAX的基礎知識,它包括:
? ? ? (1)DAX語法;
? ? ? (2)DAX可以處理的不同數據類型;(見第10式)
? ? ? (3)DAX基本操作符;(見第11式)
? ? ? (4)DAX如何引用列或表。其中,最后一點是重點,己包含最多內容。
? ? ? 下面我們將討論這些概念。以便我們能運用列表來定義計算。
? ? ? 我們已經知道,使用DAX計算表中列的值,這可以聚合、計算或檢索數字等,但是最后:所有的計算都將涉及到列或表(單列表或多個列表)。
? ? ? 因此,學習DAX的第一個語法是如何引用表中的列表。這很簡單,一般的格式是先引用表名(單引號,智能提示中雙擊選擇的表即可),再是該表的某個列的列名(方括號,同上方法即可),如:
? ? ? 'Sales'[Sale] -- 請求引用Sales表的[Sale]列
? ? ? ? 當然,同一個表中,直接:[Sale],也是一個有效的列引用。當然,計算列或在Sales表中的度量也可以這樣寫。
? ? ? 即使該寫法在語法上是正確的,我們也建議你不要使用它。這樣的語法將使得代碼很難閱讀(有時候分不清到底是一個度量還是一個元列)。
? ? ? 所以,當DAX表達式中引用列時,最好總是使用表名( 'Sales'[Sale] ),也叫顯式引用某列,即很清晰的告訴DAX:你請求引用Sales表的[Sale]列,而不是任何其他的列)。一旦你引用了某個列,通常都會將它裝入CALCULATE容器里,作為CALCULATE的某個計算參數,以定義一個計算數據模型列表。
? ? ? ? 本部分主要也涉及DAX中的基礎語法知識,以便能正確的寫出需要的DAX公式,DAX詳細的語法,請參閱官方幫助文件。
? ? 第14式:CALCULATE對值列表、列表的選擇
? ? ? CALCULATE在定義計算公式時,始終操縱的是列表,只是不同的位置需要不同的列表狀態(即值列表或列表)。暫時的你只要記得,需要引用列表的參數位置,不能是值列表,而需要定義值列表的位置,則不能是列表。這是前面我們已證明的,關于值列表與列表對應的兩個方面:
? ? (1)值列表或列表的篩選; // 對數據模型內部列表的篩選;
? ? (2)值列表或列表的計算。 // CALCULATE()公式。
? ? ? 所以,有關DAX的問題,我們現在暫時都可以圍繞篩選與計算兩方面進行。值列表與列表,不管你承認與否,本質上還是為計算提供行、列坐標,它畢竟是由單元格進化而來。不過相比單元格,它具有強大的引擎和列式數據模型的快速計算等等的特點。
? ? ? 我們約定:無論是否存在關系,一個字段的整列(包含全部基數行)稱之為列表;涉及該列其中的一個或多個行列值的列稱為值列表(可能只包含該列中的部分基數行)。
? ? ? 布爾值,因為其TRUE/FLASE的結果范圍也關聯到基數行。因此,我們將布爾值也視為值列表。某種意義上說,它們是相同的。
? ? ? 一行多列或多行多列指代多個列表時,我們稱之為表。不作特別說明,表指代多個列,列表指單個列。
? ? ? 我們暫時運用CALCULATE( )的:不同位置需要不同的列表狀態(即值列表或列表)來解釋一些公式行為:
? ? ? 1、最熟悉的莫過于CALCULATE(第一參數)與CALCULATETABLE(第一參數),其第一參數分別是值列表與列表。 例如:
? ? (1)CALCULATE(值列表或結果為值列表的計算式);
? ? (2)CALCULATETABLE(列表或結果為列表的計算式)
? ? ? ? 當然,你知道FILTER(第一參數)也是列表。所以,某些場景下(例如第一參數為列表)時,使用FILTER與使用CALCULATETABLE沒有區別,不過CALCULATETABLE的計算順序不同于FILTER。在FILTER函數部分將會涉及。
? ? ? ? 2、前面第一部分提到的計算列與度量公式的隱式行篩選與顯式列表篩選行為,如果你比較難理解,你也可以值列表與列表在這兩種情形下的不同行為來理解:比如度量的第一參數,以及它的結果都需要是值列表(請不要將它理解為結果為值,它的結果是列表,而且是值列表??赡苁且粋€行的值列表,也可能是多個行的值列表)而不是列表。計算列的結果當然只能是列表,本來就是希望新增一個列表。
? ? ? 用列表與值列表的特點也能理解:正因為度量是值列表結果,因此,它具有行屬性行為特點(列表基數可以任意的變化--即接受篩選,從而滿足計算的需要)。?
? ? ? 比如,你可以將度量乘以一個適當的數值,以獲得一個在任何篩選條件下,總會在該元度量基礎上獲得1.15倍的值。
? ? ? 而計算列是列表,它操作的是該列的整個列基數(唯一值)。
? ? ? 當列表篩選或被篩選發生時,實際都是整列的基數的變化(當相同基數的兩列篩選時,實際上篩選并沒有發生或者篩選不起作用)。
? ? ? ? 在度量列里寫上:列-列 ,表示我們從數據模型里“引用”了這兩個列。從原理上說,你并沒有指示引擎下一步該如何做(相當于我們只是從壓縮數據中取出兩個列表,加載到內存存儲起來,公式引擎不起作用,即使引擎執行了隱式的行行為,也是整個列表,或者說這時候的值列表等于列表--基數沒有變化)。?
? ? ? 所以,這時候,其實它是兩個列表而不是值列表。因而我們說,它需要你定義一個值列表的行的行為(如使用SUM、MIN等)。這樣,公式引擎將自動執行隱式的行的行為。
? ? ? ? 而在計算列里寫上:列=列,表示我們從數據模型里“引用”了這兩個列,由于計算列在當前的表里,引擎執行的隱式的行行為,這時候它是兩個值列表,因而能夠計算。
? ? ? 再簡單點理解,涉及計算列和度量兩個概念:
? ? ? 1)計算列的表達式分別對表的每一行進行計算,其結果與其他列值一樣被存儲在表中。刷新表內容將對該表的計算列更新所有列值,而不必考慮表的修改部分。因此,所有計算列占用內存空間,并在列表處理期間僅計算一次。
? ? ? 為計算列定義的DAX表達式,是在它所屬的表的當前列表篩選中操作。任何對該列的引用都將返回當前該列的整個列值,而不能直接訪問其他列值。
? ? ? 2)度量值是在由數據模型的一組列值所在的當前列表下計算的DAX表達式。在DAX列表查詢中使用的函數會生成不同的列表篩選:用于在度量中定義計算的DAX表達式或由查詢本身中定義的原數據模型列表。
? ? ? ? 此外,數據模型表的每個列值都定義了一個隱式的列表篩選子集,也就是說,對于數據模型表本身的每個列值都是不同的。例如,取決于透視表中的用戶選擇的當前列值。
? ? ? ? 所以,當你使用SUM([SalesAmount]) 時,你指的是在這個列表集下聚集的所有行的和;而當我們在計算列中使用[SalesAmount]時,我們指的是當前列值:SalesAmount的列值(值列表,可能是整列的值列表,或者是每個列值構成的一個或多個值列表)。
? ? ? ? 我們一次一次地說這個概念,都是一次次說不明白,等到恰當的時候,也許你突然就明白了。
? ? ? ? 這里,我們只要記得,度量與計算列的不同列表參數行為。并且總是以度量行為為主就行。你也可以回顧與結合一下第一部分的介紹。這是被引用過的圖:
? ? ? ? 3、VAR變量(請參閱《變量系列》):VAR可以定義列表或值列表。但一般情況都是定義為值列表,因為列表可以直接引用數據模型里的元列表(如果定義為列表,則一般是:結果為列表的度量或計算式定義)。一旦定義,你就可以在公式中使用 RETURN 取出該定義的列表參與計算。
? ? ? ? 4、在DAX公式不同函數的不同參數中,需要不同的值列表或列表。例如上圖中FILTER的第一參數是一個表,以及構建邏輯篩選條件的兩個值列表篩選器。這部分后面將有太多的案例出現,并將解釋篩選轉換如何與 DAX 中的CALCULATE函數的篩選器參數進行交互。這一點很重要,它能避免在篩選器參數中,進行復雜計算時出現意外結果。
? ? ? ? 先觀察一個DAX計算案例公式的幾種變體,我們運用之前有關值列表與列表的行為,來判斷公式是否正確。
? ? ? ? 業務場景:計算兩年內每一年的銷售額,并只考慮這兩年中存在業務記錄的那些月份。
? ? ? ? 根據業務要求,我們模擬DAX的可能條件是:為正確得出2009年的度量結果,一個很重要的前提是,必須考慮該年份中內有銷售數據的8月和12月。
? ? ? 出于實驗,我們不使用時間智能函數,首先我們寫出第一個度量公式:
? ? ? 要解釋這個公式行為,需要很多時間以及花很多的文字。這里,我們只是想使用值列表與列表的行為,來直接判斷公式是否正確,如果錯誤,錯在哪里? 當然該公式結論是錯誤的。
? ? ? ? 公式中,直觀上,灰底的SELECTEDVALUE ()部分,無論是作為TREATAS的參數,還是與TREATAS組成新的篩選,再作為CALCULATE的參數,這里需要的是一個列表篩選,而不是行篩選。簡單說,該位置需要一個列表而不是值列表。? ? ?
? ? ? ? 這時候,通過之前的學習,我們有兩種可能的變通方法來解決這個問題:
? ? ? 第一種:使用CALCULATE強制將整個SELECTEDVALUE ()部分,顯式引用為列表(即另一個說法:將行篩選轉化為等效的列表篩選)。?
? ? ? 第二種解決方法。在該位置上使用現有的行篩選。CALCULATE的篩選器參數沒有接收到任何篩選轉換定義指令,因此任何行篩選仍然可用(使用的是[YearMonth]列的整列的基數值,當然,這實際就是列表)。? ? ? ?
? ? ? 最后,我們結束實驗,以標準的時期智能函數來解決完成這個公式。如果你已經學習過時間智能函數,應該知道,任何時間智能函數,都有一個對應的使用標準時期參數的CALCULATE公式。
? ? ? ? 下圖是模擬使用上述四個公式得出的結果表,其中第一個公式,由于多重選擇,SELECTEDVALUE 返回空白,從而公式結果也返回空白。
? ? ? ? 當然,你可以列舉出許多這方面的案例。這里從略。
? ? ? ? 前面,我們在為DAX的兩大主題之一:計算,準備著相關的需要掌握的內容。接下來,我們將針對另一主題:列表篩選。? ? ? ? ?
? ? ? ? 未完待續