2.4 表達(dá)式中的錯誤處理

第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á)式中的值BLANKBLANK不同于空字符串。例如,以下表達(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的模式,該模式需要同時使用ISERRORIF

= 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ù)為BLANKISBLANK檢測到空值,則返回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." )
)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容