第2章 DAX簡介
2.1 理解DAX計(jì)算
2.2了解計(jì)算列和度量值
2.3 變量入門
2.4 表達(dá)式中的錯誤處理
2.5 格式化DAX代碼
2.6 聚合函數(shù)和迭代函數(shù)
2.7 使用常見的DAX函數(shù)
表達(dá)式中的錯誤處理
既然已經(jīng)了解了語法的一些基礎(chǔ)知識,是時候?qū)W習(xí)如何優(yōu)雅地處理無效計(jì)算了。DAX表達(dá)式可能包含無效的計(jì)算,因?yàn)樗玫臄?shù)據(jù)對該公式無效。例如,該公式可能包含被零除或在諸如乘法之類的算術(shù)運(yùn)算中引用不是數(shù)字的列值。最好了解默認(rèn)情況下如何處理這些錯誤以及如何攔截這些錯誤以便進(jìn)行處理。
但是,在討論如何處理錯誤之前,我們先描了DAX公式運(yùn)算期間可能出現(xiàn)的各種錯誤。它們是:
- 轉(zhuǎn)換錯誤
- 算術(shù)運(yùn)算錯誤
- 空值或缺少值
轉(zhuǎn)換錯誤
第一種錯誤是轉(zhuǎn)換錯誤。如本章前述,DAX在運(yùn)算符需要時會自動在字符串和數(shù)字之間轉(zhuǎn)換值。以下這些示例都是有效的DAX表達(dá)式:
"10" + 32 = 42
"10" & 32 = "1032"
10 & 32 = "1032"
DATE (2010,3,25) = 3/25/2010
DATE (2010,3,25) + 14 = 4/8/2010
DATE (2010,3,25) & 14 = "3/25/201014"
這些公式始終正確,因?yàn)樗鼈円院愣ㄖ颠\(yùn)行。但是,如果VatCode是字符串,那么下面的公式呢?
Sales[VatCode] + 100
因?yàn)榇饲蠛凸降牡谝粋€操作數(shù)是Text數(shù)據(jù)類型的列,所以作為開發(fā)人員,必須確信DAX可以將該列中的所有值轉(zhuǎn)換為數(shù)字。如果DAX無法將某些內(nèi)容轉(zhuǎn)換為適合運(yùn)算符需要的內(nèi)容,則會發(fā)生轉(zhuǎn)換錯誤。以下是一些典型情況:
"1 + 1" + 0 = Cannot convert value '1 + 1' of type Text to type Number
DATEVALUE ("25/14/2010") = Type mismatch
如果要避免這些錯誤,在DAX表達(dá)式中添加錯誤檢測邏輯以攔截錯誤條件并返回有意義的結(jié)果非常重要。可以在發(fā)生錯誤后攔截錯誤或事先檢查操作數(shù)以了解錯誤情況來獲得相同的結(jié)果。但是,主動檢查錯誤情況比讓錯誤發(fā)生然后捕獲要好。
算術(shù)運(yùn)算錯誤
第二類錯誤是算術(shù)運(yùn)算錯誤,例如被零除或負(fù)數(shù)求平方根。這些不是與轉(zhuǎn)換相關(guān)的錯誤:使用無效數(shù)值調(diào)用函數(shù)及運(yùn)算符時,DAX都會引發(fā)算術(shù)運(yùn)算錯誤。
被零除需要特殊處理,因?yàn)樗男袨椴恢庇^(數(shù)學(xué)家可能除外)。當(dāng)一個數(shù)字除以零時,DAX返回特殊值Infinity(無窮大)。在0除以0或Infinity除以Infinity的特殊情況下,DAX返回特殊的NaN(不是數(shù)字)值。這些異常行為在表2-3中進(jìn)行了總結(jié)。
表 2-3 被零除的特殊結(jié)果
表達(dá)式 | 結(jié)果 |
---|---|
10/0 | Infinity |
7/0 | Infinity |
0/0 | NaN |
(10/0)/(7/0) | NaN |
重要的是要注意,Infinity和NaN并不是錯誤,而是DAX中的特殊值。實(shí)際上,如果將數(shù)字除以Infinity,則表達(dá)式不會產(chǎn)生錯誤。而是返回0:
9954 / ( 7 / 0 ) = 0
除了這種特殊情況外,當(dāng)使用不正確的參數(shù)(例如負(fù)數(shù)的平方根)調(diào)用函數(shù)時,DAX可能返回算術(shù)錯誤:
SQRT ( -1 ) = An argument of function 'SQRT' has the wrong data type or the result is too
large or too small
如果DAX檢測到這樣的錯誤,它將阻止對該表達(dá)式進(jìn)一步運(yùn)算,并引發(fā)錯誤。可以使用ISERROR函數(shù)檢查表達(dá)式是否導(dǎo)致錯誤。我們將在本章后述這種情況。
請記住,象NaN這樣的特殊值會在Power BI等工具的用戶界面中顯示為正常值。但是,其他客戶端工具如Excel pivot table(Excel數(shù)據(jù)透視表)顯示這些值時,可能視為錯誤。最終,用錯誤檢測函數(shù)檢測這些特殊值均為錯誤。
空值或缺失值
第三類不是特定的錯誤情況,而是存在空值。當(dāng)空值與計(jì)算中的其他元素結(jié)合使用時,可能會導(dǎo)致意外結(jié)果或計(jì)算錯誤。
DAX以相同的方式處理缺失值、空值或空單元格,即使用值BLANK(空值)。BLANK不是真正的值,而是識別這些情況的一種特殊方法。我們可以通過調(diào)用BLANK函數(shù)來獲得DAX表達(dá)式中的值BLANK,BLANK不同于空字符串。例如,以下表達(dá)式始終返回一個空值,該空值可以在不同的客戶端工具中顯示為空字符串或"(blank)":
= BLANK ()
單獨(dú)使用此表達(dá)式?jīng)]什么用處,但需要返回空值時,BLANK函數(shù)就會會變得有用,例如,可能要顯示一個空結(jié)果而不是0。以下表達(dá)式計(jì)算銷售交易的總折扣,如果折扣為0,則保留空白值:
=IF (
Sales[DiscountPerc] = 0, -- Check if there is a discount
BLANK (), -- Return a blank if no discount is present
Sales[DiscountPerc] * Sales[Amount] -- Compute the discount otherwise
)
空值本身并不是一個錯誤;它只是一個空值。因此,包含BLANK的表達(dá)式可能會返回值或空值,具體取決于計(jì)算式。例如,下面的表達(dá)式當(dāng)Sales[Amount]是 BLANK時 返回 BLANK:
= 10 * Sales[Amount]
換句話說,只要一項(xiàng)或兩項(xiàng)為BLANK,則算術(shù)積的結(jié)果為BLANK。這在需要檢查空值時會帶來挑戰(zhàn)。由于存在隱式轉(zhuǎn)換,因此無法使用等于運(yùn)算符(=)區(qū)分表達(dá)式是0(或空字符串)還是BLANK。實(shí)際上,以下邏輯條件始終是正確的:
BLANK () = 0 -- Always returns TRUE
BLANK () = "" -- Always returns TRUE
因此,如果Sales [DiscountPerc]或Sales [Clerk]列為空,即使分別針對0和空字符串測試,以下條件也會返回TRUE:
Sales[DiscountPerc] = 0 -- Returns TRUE if DiscountPerc is either BLANK or 0
Sales[Clerk] = "" -- Returns TRUE if Clerk is either BLANK or ""
在這種情況下,可以使用ISBLANK函數(shù)檢查值是否為BLANK:
ISBLANK ( Sales[DiscountPerc] ) -- Returns TRUE only if DiscountPerc is BLANK
ISBLANK ( Sales[Clerk] ) -- Returns TRUE only if Clerk is BLANK
DAX表達(dá)式中BLANK的傳遞還發(fā)生在其他幾種算術(shù)和邏輯運(yùn)算中,如以下示例所示:
BLANK () + BLANK () = BLANK ()
10 * BLANK () = BLANK ()
BLANK () / 3 = BLANK ()
BLANK () / BLANK () = BLANK ()
但是,并不是公式都會在表達(dá)式結(jié)果中傳遞BLANK。某些計(jì)算不會傳遞BLANK,而是根據(jù)公式的其他條件返回一個值。這些示例包括加減法,BLANK除法以及包括BLANK的邏輯運(yùn)算。以下表達(dá)式顯示了其中一些條件及其結(jié)果:
BLANK () ? 10 = ?10
18 + BLANK () = 18
4 / BLANK () = Infinity
0 / BLANK () = NaN
BLANK () || BLANK () = FALSE
BLANK () && BLANK () = FALSE
( BLANK () = BLANK () ) = TRUE
( BLANK () = TRUE ) = FALSE
( BLANK () = FALSE ) = TRUE
( BLANK () = 0 ) = TRUE
( BLANK () = "" ) = TRUE
ISBLANK ( BLANK() ) = TRUE
FALSE || BLANK () = FALSE
FALSE && BLANK () = FALSE
TRUE || BLANK () = TRUE
TRUE && BLANK () = FALSE
Excel和SQL中的空值
Excel具有處理空值的另一種方法。在Excel中,將所有空值用于求和或乘法時都將被視為0,但是如果它們是除法或邏輯表達(dá)式的一部分,則它們可能會返回錯誤。
在SQL中,空值在表達(dá)式中的傳遞方式與DAX中BLANK發(fā)生的方式不同。從前面的示例中可以看到,DAX表達(dá)式中存在BLANK并不總是導(dǎo)致BLANK結(jié)果,而SQL 中NULL的存在對于整個表達(dá)式通常為NULL。每當(dāng)在關(guān)系數(shù)據(jù)庫上使用DirectQuery時,這種區(qū)別就很重要,因?yàn)槟承┯?jì)算在SQL中執(zhí)行,而其他計(jì)算在DAX中執(zhí)行。兩個引擎中BLANK的語義不同可能會導(dǎo)致意外行為。
了解DAX表達(dá)式中空值或缺失值的行為以及在計(jì)算中使用BLANK返回空單元格是控制DAX表達(dá)式結(jié)果的重要技能。正如我們在下一節(jié)中演示的那樣,當(dāng)檢測到錯誤的值或其他錯誤時,通常可以使用BLANK作為結(jié)果。
攔截錯誤
既然已經(jīng)詳細(xì)介紹了可能發(fā)生的各種錯誤,接下來還需要向您介紹攔截錯誤并糾正錯誤的技術(shù),至少出現(xiàn)錯誤時返回有意義錯誤消息。DAX表達(dá)式中是否存在錯誤,通常取決于表達(dá)式本身中使用的列值。因此,要控制產(chǎn)生錯誤的條件并返回錯誤消息。標(biāo)準(zhǔn)技術(shù)是檢查表達(dá)式是否返回錯誤,如果是,則將錯誤替換為特定的消息或默認(rèn)值。有幾個DAX函數(shù)具備此功能。
第一個是IFERROR函數(shù),它與IF函數(shù)類似,但是它不評估布爾條件,而是檢查表達(dá)式是否返回錯誤。IFERROR函數(shù)的兩種典型用法如下:
= IFERROR ( Sales[Quantity] * Sales[Price], BLANK () )
= IFERROR ( SQRT ( Test[Omega] ), BLANK () )
在第一個表達(dá)式中,如果Sales [Quantity]或Sales [Price]是無法轉(zhuǎn)換為數(shù)字的字符串,則表達(dá)式返回空值。否則,返回?cái)?shù)量和價格的乘積。
在第二個表達(dá)式中,當(dāng)TestΩ列包含負(fù)數(shù)時,結(jié)果返回一個空單元格。
還有一個更通用的使用IFERROR的模式,該模式需要同時使用ISERROR和IF:
= IF (
ISERROR ( Sales[Quantity] * Sales[Price] ),
BLANK (),
Sales[Quantity] * Sales[Price]
)
= IF (
ISERROR ( SQRT ( Test[Omega] ) ),
BLANK (),
SQRT ( Test[Omega] )
)
對于這些情況,IFERROR是更好的選擇。只要返回結(jié)果的表達(dá)式與錯誤測試的表達(dá)式相同,就可以使用IFERROR;無需在兩個地方重復(fù)該表達(dá)式,并且代碼更安全,更易讀。但是,開發(fā)人員要返回其他表達(dá)式的結(jié)果時應(yīng)使用IF。
此外,完全可以通過在使用參數(shù)之前測試參數(shù)來減少錯誤的出現(xiàn)。例如,可以檢測SQRT的參數(shù)是否為正,并為負(fù)值返回BLANK:
= IF (
Test[Omega] >= 0,
SQRT ( Test[Omega] ),
BLANK ()
)
考慮到IF語句的第三個參數(shù)默認(rèn)為BLANK,也可以編寫簡潔的等效表達(dá)式:
= IF (
Test[Omega] >= 0,
SQRT ( Test[Omega] )
)
一種常見的情況是空值測試。如果其參數(shù)為BLANK,ISBLANK檢測到空值,則返回TRUE。此函數(shù)特別重要,尤其是當(dāng)不可用的值并不表示其為0時。以下示例說明,如果交易本身未指定重量,使用交易的默認(rèn)運(yùn)費(fèi)來計(jì)算銷售交易的運(yùn)費(fèi):
= IF (
ISBLANK ( Sales[Weight] ), -- If the weight is missing
Sales[DefaultShippingCost], -- then return the default cost
Sales[Weight] * Sales[ShippingPrice] -- otherwise multiply weight by shipping price
)
如果我們簡單地將產(chǎn)品重量乘以運(yùn)輸價格,那么由于BLANK的乘積傳遞,所有沒有重量數(shù)據(jù)的銷售交易的運(yùn)費(fèi)為空。
使用變量時,必須在定義變量而不是使用變量時檢查錯誤。實(shí)際上,以下代碼中的第一個公式返回零,第二個公式始終出現(xiàn)錯誤,最后一個公式根據(jù)使用的DAX版本產(chǎn)生不同的結(jié)果(最新版本也出現(xiàn)錯誤):
IFERROR ( SQRT ( -1 ), 0 ) -- This returns 0
VAR WrongValue = SQRT ( -1 ) -- Error happens here, so the result is
RETURN -- always an error
IFERROR ( WrongValue, 0 ) -- This line is never executed
IFERROR ( -- Different results depending on versions
VAR WrongValue = SQRT ( -1 ) -- IFERROR throws an error in 2017 versions
RETURN -- IFERROR returns 0 in versions until 2016
WrongValue,
0
)
錯誤產(chǎn)生在評估WrongValue的時候,因此,在第二個示例中,引擎將永遠(yuǎn)不會執(zhí)行IFERROR函數(shù),而第三個示例的結(jié)果取決于產(chǎn)品版本。如果需要檢查錯誤,請?jiān)谑褂米兞繒r采取一些額外的預(yù)防措施。
避免使用錯誤處理函數(shù)
盡管我們將在本書的后面部分會介紹優(yōu)化,但是您需要意識到錯誤處理函數(shù)可能會在代碼中造成嚴(yán)重的性能問題。并不是說這些函數(shù)本身很慢。問題在于,發(fā)生錯誤時,DAX引擎無法在其代碼中使用優(yōu)化的路徑。在大多數(shù)情況下,檢查操作數(shù)是否存在錯誤比使用錯誤處理引擎更有效。例如,不要這樣寫:IFERROR ( SQRT ( Test[Omega] ), BLANK () )
最好這樣寫:
IF ( Test[Omega] >= 0, SQRT ( Test[Omega] ), BLANK () )
第二個表達(dá)式不需要檢測錯誤,并且比前一個表達(dá)式快。當(dāng)然,這是一般規(guī)則。有關(guān)詳細(xì)說明,請參見第19章”優(yōu)化DAX”。
避免使用IFERROR的另一個原因是它無法攔截更深層次的錯誤。例如,以下代碼攔截了Table表的Amount列不包含數(shù)字的情況下轉(zhuǎn)換為空值時發(fā)生的任何錯誤,如前所述,此執(zhí)行代價很高,因?yàn)樾枰獙able中的每一行進(jìn)行評估。SUMX ( Table, IFERROR ( VALUE ( Table[Amount] ), BLANK () ) ) )
請注意,由于DAX引擎中的優(yōu)化,以下代碼不會截獲與前面示例相同的錯誤。如果Table [Amount]僅在一行中包含不是數(shù)字的字符串,則整個表達(dá)式將生成IFERROR不會攔截的錯誤。
IFERROR ( SUMX ( Table, VALUE ( Table[Amount] ) ), BLANK () )
ISERROR 具有與 IFERROR 相同的行為。確保謹(jǐn)慎使用它們,并且僅將IFERROR / ISERROR用于攔截表達(dá)式直接引發(fā)的錯誤,不要在用在嵌套計(jì)算中。
產(chǎn)生錯誤
有時,錯誤就錯誤,在錯誤出現(xiàn)的情況下,公式不應(yīng)返回默認(rèn)值。實(shí)際上,返回默認(rèn)值實(shí)際上會導(dǎo)致不正確結(jié)果。例如,包含不一致數(shù)據(jù)的配置表應(yīng)生成無效的報告,而不是不可靠的數(shù)字,生成無效的報告可以認(rèn)為是正確的。
而且,除了一般錯誤之外,人們可能希望產(chǎn)生一條對用戶更有意義的錯誤消息。這樣的消息將幫助用戶找到問題所在。
考慮一個場景,該場景需要計(jì)算以開爾文為單位的絕對溫度的平方根,以在復(fù)雜的科學(xué)計(jì)算中大致調(diào)整聲音的速度。顯然,我們不希望溫度為負(fù)數(shù)。如果碰巧由于測量問題而發(fā)生這種情況,我們需要提出錯誤并停止計(jì)算。在這種情況下,此代碼很危險,因?yàn)樗[藏了問題:
= IFERROR (
SQRT ( Test[Temperature] ),
0
)
相反,為了保護(hù)計(jì)算,應(yīng)該這樣編寫公式:
= IF (
Test[Temperature] >= 0,
SQRT ( Test[Temperature] ),
ERROR ( "The temperature cannot be a negative number. Calculation aborted." )
)