DAX 權威指南 | 05 理解CALCULATE 和 CALCULATETABLE

在本章中乃坤,我們將繼續(xù)探索DAX語言的強大葛家,并詳細解釋單個函數(shù):CALCULATE煞抬。實際上县好,相同的注意事項對于CALCULATETABLE也是有效的,它計算并返回表而不是標量值禀倔。為簡單起見榄融,我們將在示例中引用CALCULATE,但請記住CALCULATETABLE具有相同的行為救湖。

用一整章的篇幅來描述一個函數(shù)似乎有些奇怪愧杯,但這是必要的,因為它的豐富性和副作用鞋既。 CALCULATE是迄今為止DAX語言中最重要力九、最有用和最復雜的函數(shù)。實際上涛救,函數(shù)本身很簡單畏邢。它只執(zhí)行很少的任務,但是需要計算的場景的數(shù)量检吆,以及可以用計算編寫的公式的復雜性,使得完整的章節(jié)是絕對必要的程储。

正如前一章所述蹭沛,這是一個艱難的過程。我們強烈建議您閱讀一次章鲤,大致了解一下CALCULATE摊灭,然后繼續(xù)閱讀該書的剩余部分。然后败徊,當你對一個特定的公式感到困惑時帚呼,回到這一章,從頭再讀一遍皱蹦。您每次閱讀時都可能會發(fā)現(xiàn)新信息煤杀。

本章的另一個重要方面是我們需要有點迂腐。因此沪哺,如果在某個時候沈自,你發(fā)現(xiàn)某個部分看起來很無聊,而且它似乎只是在陳述顯而易見的東西辜妓,那么再仔細閱讀一遍枯途,以確保你完全理解了它忌怎。

5.1 理解CALCULATE

在上一章中,您已經了解到有兩種不同的上下文:行上下文和篩選上下文酪夷。您已經了解到可以使用迭代器以編程方式創(chuàng)建行上下文榴啸,并且學習了允許忽略篩選上下文的ALL函數(shù)。重要的是要記住ALL忽略篩選上下文晚岭,它不會改變它鸥印。因此,在以下公式中:

[Sales Amount Margin] :=
SUMX (
ALL ( Sales ),
Sales[SalesAmount] * AVERAGE ( Sales[MarginPct] )
)

ALL忽略現(xiàn)有的篩選上下文并始終返回整個表腥例,但它不會以任何方式更改公式的其他部分的計算辅甥。實際上,在最內層表達式中燎竖,AVERAGE將計算篩選上下文中MarginPct列的平均值璃弄,DAX在該上下文中計算整個表達式。 DAX中有一個函數(shù)可以更改篩選上下文构回,它是CALCULATE夏块。

讓我們通過查看一個很有用的場景來介紹CALCULATE。想象一下纤掸,您想要構建如圖5-1所示的報告脐供,其中包含類別,子類別和銷售額總和借跪。

圖5-1在這里政己,您可以看到一個簡單的報告,顯示銷售額除以類別和子類別掏愁。 SalesPct列顯示行總數(shù)的百分比歇由。

報告顯示了每行占總行的百分比。您可以使用Microsoft Excel數(shù)據(jù)透視表功能輕松地生成這樣的報告果港,但是我們感興趣的是計算百分比作為度量值沦泌,以便用戶可以隨時將其添加到數(shù)據(jù)透視表中。

以下是一個天真的解決方案:

SalesPct :=
DIVIDE (
SUM ( Sales[SalesAmount] ),
SUMX ( ALL ( Sales ), Sales[SalesAmount] )
)

分子是SalesAmount的和辛掠。分母忽略篩選上下文谢谦,并始終返回SalesAmount的總計,而不管任何篩選器萝衩。只要您不從切片器中選擇任何內容回挽,此公式就可以使用。例如欠气,如果在切片器中選擇黑色厅各,則值為錯誤;總計的百分比是18.76%而不是100%预柒,因為用于百分比計算的分母是一個更高的數(shù)字队塘,如圖5-2所示袁梗。

圖5-2從切片器中選擇顏色顯示錯誤的百分比結果。

這里的問題很容易理解憔古。通過使用ALL遮怜,我們忽略了篩選上下文。因此鸿市,分母始終是所有銷售的總計锯梁,而如果選擇顏色,則希望將篩選器保持為顏色焰情,僅清除類別和子類別上的篩選器陌凳。 ALL和迭代器不是這里的正確選擇;你需要更強大的東西内舟。換句話說合敦,您需要CALCULATE。

4.1.1 理解篩選上下文

在繼續(xù)介紹CALCULATE之前验游,插入一個題外話充岛,這很重要,以加深您對篩選上下文的理解耕蝉。篩選上下文是一個復雜的概念崔梗,只有在第10章“高級計算上下文”的末尾,您才能最終理解它是如何工作的垒在。在此之前蒜魄,我們按照復雜性的順序對篩選上下文的進行了不同的描述,以便一步一步地得到最終的解釋场躯。

在上一章中权悟,我們給出了是啊選上下文的第一個定義:“應用于模型的一組篩選器,它們更改了整個數(shù)據(jù)庫中可見的行推盛。”即使它是一個正確的定義谦铃,它仍然非常幼稚耘成。為了進入下一個級別,您需要記住VertiPaq(DAX工作的數(shù)據(jù)庫)是一個列式數(shù)據(jù)庫驹闰。您應該停止考慮表格瘪菌,而不是考慮列。

例如嘹朗,您可以將Product視為常規(guī)表师妙,如圖5-3所示。

圖5-3您可以將Product表視為標準表屹培,由行組成默穴,每行分為列怔檩。

但是,由于VertiPaq是一個列式數(shù)據(jù)庫蓄诽,因此表的正確表示形式將是一組列薛训,而不是單個實體。因此仑氛,如圖5-4所示可以更好的理解同一個表

圖5-4 Product的正確可視化是一組列乙埃,每列都分為幾行。

當然锯岖,內容是相同的介袜,但是現(xiàn)在更容易將不同的列看作存儲在內存中的不同項。顯然出吹,相同的表示適用于數(shù)據(jù)模型中的任何表遇伞。因此,您應該在思維上將模型中的每個表劃分為單獨的列趋箩,最后將得到一組列赃额,邏輯上劃分為表,但每列與其他列分開叫确。

將Color放入切片器時跳芳,DAX會對該列應用篩選器。請仔細閱讀:它不會對包含該列的表格應用篩選器竹勉。它僅將篩選器應用于列飞盆。然后,因為該列是表的一部分次乓,因此該表也將具有篩選器吓歇。盡管如此,篩選器一次只能處理一列(我們正在描述實際發(fā)生的事情的近似票腰;我們將在本書的后面部分對篩選上下文進行最終理解)城看。

當您僅在切片器篩選中選擇紅色產品時,模型將具有此篩選器杏慰,如圖5-5所示测柠。

圖5-5過濾紅色產品會導致僅應用于“顏色”列的過濾器。

您可以想象將單個列上的篩選器表示為列的值上的位圖缘滥,或者以一種更易于閱讀的方式表示為列的活動值列表轰胁。

最后,我們可以更好地定義篩選上下文:篩選器上下文是一組表朝扼。每個表都包含一個列赃阀,并列出該列的所有值,引擎認為這些值在當前上下文中可見擎颖。所有這些篩選器榛斯,放在一個邏輯AND中观游,形成篩選上下文。

在數(shù)據(jù)透視表的單個單元格中肖抱,您有來自切片器和篩選的篩選器备典;從行和列。每個篩選器都對一組表列進行操作意述。因此提佣,在前面的示例中,篩選上下文包含三個單獨的篩選器:一個用于類別荤崇,一個用于子類別(兩者都在數(shù)據(jù)透視表中的行上)拌屏,另一個用于顏色(來自切片器)。

所有這些題外話使我們更好地理解更改篩選上下文的含義术荤。如果要更改篩選上下文倚喂,則需要為模型中的部分或全部篩選列提供新的值列表。 DAX將使用新的值列表替換該列上的篩選器(僅限該列)瓣戚,并以此方式生成新的篩選上下文端圈。

要記住的兩個重要方面是:

  • 篩選器是一列的一組活動值。
  • 篩選器始終僅適用于單個列子库。

請記住舱权,這不是篩選上下文的正確定義。在成為DAX的大師之前仑嗅,你必須學習許多其他方面的知識宴倍。然而,這個定義對于開始使用篩選上下文已經非常有用了仓技。

在結束了題外話之后鸵贬,您現(xiàn)在對篩選上下文有了更好的理解,我們可以繼續(xù)介紹CALCULATE是如何修改它的脖捻。

4.1.2 介紹CALCULATE

CALCULATE(及其同伴CALCULATETABLE阔逼,您將在稍后學習)是唯一可以修改篩選上下文的函數(shù)。實際上地沮,CALCULATE會創(chuàng)建一個新的篩選上下文颜价,然后在該新上下文中計算表達式。因為新上下文的起點是現(xiàn)有上下文诉濒,我們可以說它修改了計算上下文。

讓我們開始檢查CALCULATE的語法:

[Measure] := CALCULATE ( Expression, Condition1, ...
ConditionN )

CALCULATE接受任意數(shù)量的參數(shù)夕春,唯一的強制參數(shù)是第一個未荒,即要計算的表達式。我們在第一個參數(shù)篩選器參數(shù)后面調用條件及志。 CALCULATE執(zhí)行以下操作:

  • 它獲取當前篩選上下文并將其復制到新的篩選上下文中片排。
  • 它計算每個篩選器參數(shù)寨腔,并為每個條件生成該特定列的有效值列表。
  • 如果兩個或多個篩選器參數(shù)影響同一列率寡,則使用AND運算符將它們合并在一起(或者迫卢,在數(shù)學術語中,使用集合交集)冶共。
  • 它使用新條件替換模型中列上的現(xiàn)有篩選器乾蛤。如果列已有過濾器,則新過濾器將替換現(xiàn)有過濾器捅僵。另一方面家卖,如果列沒有篩選器,則 DAX只是將新篩選器應用于列庙楚。
  • 一旦計算了新的篩選上下文上荡,CALCULATE將計算新篩選上下文中的第一個參數(shù)(表達式)。最后馒闷,它將恢復原始篩選上下文酪捡,返回計算機結果。

備注:
CALCULATE執(zhí)行另一項非常重要的任務:它將任何現(xiàn)有行上下文轉換為等效的篩選上下文纳账。本章稍后將對此主題進行更詳細的討論逛薇。我們在這里提到它的原因是,如果你對本節(jié)進行第二次閱讀塞祈,最好記住這個非常重要的事實:CALCULATE從現(xiàn)有的行上下文中創(chuàng)建一個篩選上下文金刁。

CALCULATE接受的篩選器可以有兩種類型:

  • 值表,以表表達式的形式议薪。在這種情況下尤蛮,您需要提供要在新篩選上下文中查看的確切值列表。篩選器可以是具有單列或多列的表斯议,就像整個表上的篩選器一樣产捞。
  • 布爾條件,例如Product [Color] =“White”哼御。這些篩選器需要在單個列上工作坯临,因為結果必須是單個列的值列表。

如果使用帶有布爾條件的語法恋昼,DAX會將其轉換為值列表看靠。所以每當你寫:

[Sales Amount Red Products] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
Product[Color] = "Red"
)

DAX將表達式轉換為以下表達式:

[Sales Amount Red Products] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
ALL ( Product[Color] ),
Product[Color] = "Red"
))

因此,您只能使用布爾條件引用篩選器參數(shù)中的一列液肌。 DAX必須檢測要在FILTER表達式中迭代的列挟炬,F(xiàn)ILTER表達式在后臺自動生成。如果布爾表達式引用了更多列,那么您必須以顯式方式編寫FILTER迭代谤祖,稍后您將看到婿滓。

在這一點上,我們可以回到計算所有類別和子類別的銷售額占總銷售額的百分比的示例粥喜,仍然要考慮顏色上的過濾器凸主。您可以使用CALCULATE編寫該度量值:
'''
[SalesPctWithCalculate] :=
DIVIDE (
SUM ( Sales[SalesAmount] ),
CALCULATE (
SUM ( Sales[SalesAmount] ),
ALL ( 'Product Category' ),
ALL ( 'Product Subcategory' )
))
'''
讓我們關注在這個公式的分母中使用的CALCULATE。要計算的表達式始終相同:銷售額的總和额湘。但是卿吐,我們已經知道公式本身可以有許多不同的值,這取決于DAX計算它的上下文缩挑。因為表達式在CALCULATE內但两,所以表達式的上下文不是原來的上下文。我們只需要理解上下文是如何進行的供置,這些信息來自于CALCULATE的附加參數(shù)谨湘。

第一個篩選參數(shù)是ALL('產品類別')。 ALL返回表中的所有行芥丧,在本例中為所有類紧阔。 DAX檢索ProductCategory表并將其值用作新篩選器,替換產品類別的任何列上的任何先前存在的篩選器续担,在此示例中擅耽,該列來自數(shù)據(jù)透視表行。

顯然物遇,對于它的第二個篩選器參數(shù)也是如此:ALL('Product Subcategory')從Product Subcategory的任何列中刪除任何篩選器乖仇。

重點是CALCULATE不會替換來自切片器的篩選器,即Color上的篩選器询兴。它僅從Product Category和Product Subcategory表的列中刪除篩選器乃沙。因此,最終篩選上下文包含所有類別和子類別诗舰,但僅包含所選顏色警儒。

在數(shù)據(jù)透視表中使用這個新公式可以顯示正確的值,如圖5-6所示眶根。


圖5-6使用CALCULATE計算的百分比顯示正確的值蜀铲。

這時,非常細心的讀者可能會停下來問:嗯属百,這沒有意義记劝。您已從Product Category和Product Subcategory中刪除了篩選器,但是您要從Sales中對值進行求和族扰。誰從該表中刪除了篩選器隆夯?這確實是一個非常好的問題钳恕。事實上,我們的描述遺漏了一些非常重要的內容:一旦CALCULATE計算出新的篩選上下文蹄衷,它就會在計算表達式之前將其應用于數(shù)據(jù)模型。

當DAX將篩選上下文應用于表時厘肮,我們已經從上一章中了解到篩選器通過遵循其定義(單向或雙向篩選)的關系出傳遞愧口。事實證明,我們從Product Category和Product Subcategory中刪除了篩選器类茂,并且當DAX應用新的篩選上下文時耍属,它將它傳遞到事實表,該事實表位于從Product Category開始的關系鏈的多端巩检,以Sales結束厚骗。通過刪除Product Category和Product Subcategory中的篩選器,我們還刪除了它們在Sales中相應的傳遞篩選器兢哭。

5.2 CALCULATE示例

現(xiàn)在您已經了解了CALCULATE的基礎知識领舰,或者至少您已經了解了它為何如此有用疾层,本章接下來的部分將專門介紹它的各種用法示例虎忌。它們對于深入學習和理解很重要苍狰。實際上斯入,CALCULATE本身就是一個非常簡單的函數(shù)蚓庭。復雜性(因此我們給它的重要性)來自這樣一個事實劳坑,即使用CALCULATE铺厨,你必須根據(jù)篩選上下文進行思考偏竟,并并且您可能會在一個公式中得到多個上下文窍株,這使得代碼流程難以理解民轴。根據(jù)我們的經驗(作為培訓師),通過示例學習是理解CALCULATE和篩選上下文的最佳方式球订。

5.2.1 篩選單個列

使用CALCULATE最簡單的方法是篩選單個列后裸。例如,假設您要創(chuàng)建一個始終返回黑色產品銷售額的度量值辙售,無論對顏色進行何種選擇轻抱。該公式非常容易得出:

[SalesAmountBlack] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
Product[Color] = "Black"
)

如果在數(shù)據(jù)透視表中使用上一個公式,您將得到如圖5-7所示的結果旦部。

圖5-7無論當前過篩選上下文如何祈搜,SalesAmountBlack始終顯示黑色產品的銷售額。

您可以看到SalesAmountBlack始終顯示黑色產品的銷售額士八,即使在行上篩選上下文選擇不同顏色也是如此容燕。

如果您關注第三行(藍色),則會發(fā)生以下情況:公式在篩選上下文中開始計算婚度,其中顏色的唯一值為藍色蘸秘。然后,CALCULATE計算了一個新條件(Color = Black),當將其應用于新的篩選器上下文時醋虏,它替換了現(xiàn)有條件寻咒,刪除了Blue上的篩選器并將其替換為Black上的篩選器。這發(fā)生在所有行(即所有顏色)上颈嚼,這就是為什么您看到所有行的相同數(shù)字的原因毛秘。

顯然,因為CALCULATE覆蓋選擇的唯一列是顏色阻课,其他列保持其篩選器叫挟。例如,如果將日歷年放在列上限煞,您會看到所有顏色的結果始終相同抹恳,但不同年份的結果會發(fā)生變化,如圖5-8所示署驻。

圖5-8 SalesAmountBlack僅覆蓋顏色;它仍然遵守其他列(年)的過濾奋献。

過濾單個列很簡單。一個不太明顯的事實是硕舆,如果您使用條件作為CALCULATE的篩選器秽荞,則一次只能篩選一列。例如抚官,您可能希望創(chuàng)建一個度量值扬跋,僅計算單位價格至少是單位成本兩倍的產品的銷售額。你可以試試這個公式:

[HighProfitabilitySales]:= CALCULATE(SUM(Sales [SalesAmount])凌节,Product [Unit Price]> = Product [Unit Cost] * 2)

您可以看到钦听,這次,條件涉及兩列:Unit Cost和Unit Price倍奢。即使DAX可以輕松計算每個產品的條件朴上,它也不允許這種語法。原因在于卒煞,在其計算算法中痪宰,CALCULATE無法確定條件是否應替換Unit Cost、Unit Price或其中任何一個上的任何現(xiàn)有過濾器畔裕。事實上衣撬,如果你試著寫上面的公式,你會得到一個錯誤的結果:

Calculation error in measure 'Sales'[HighProfitabilitySales]:
The expression contains
multiple columns, but only a single column can be used in a
True/False expression that is
used as a table filter expression.

沒有辦法使用布爾語法編寫這樣的公式扮饶。如果需要在條件中使用多個列調用CALCULATE具练,則需要使用不同的語法,它提供值列表而不是條件甜无。

編寫上一個表達式的正確方法是使用以下語法:

[HighProfitabilitySales] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER ( Product, Product[Unit Price] >= Product[Unit Cost] *
2 )
)

這次我們沒有使用布爾表達式扛点,而是使用Table語法作為CALCULATE的filter參數(shù)哥遮。而且,我們不只篩選了一列陵究;我們篩選了整個Product表眠饮。在圖5-9中,您可以看到HighProfitabilitySales度量值的實際應用铜邮。

圖5-9 HighProfitabilitySales顯示與其成本相比價格較高的產品的銷售情況君仆。

在本例中,CALCULATE計算條件:FILTER的結果是一個包含多個列的表(它包含Product的所有列)牲距。當新條件被插入到篩選上下文中時,實際上Product上的所有現(xiàn)有條件都將替換為此新篩選器钥庇。換句話說牍鞠,使用實際表作為FILTER函數(shù)的第一個參數(shù),可以有效地替換該表所有列上的所有條件评姨。

閱讀了前面的解釋后难述,您應該注意到這里有些事情不是很清楚。我們說CALCULATE中的FILTER表達式替換了Product表中所有先前存在的篩選器吐句,因為FILTER返回的表包含Product的所有列胁后。然而,我們的公式返回的值對于每一行是不同的嗦枢。

例如攀芯,在藍色行上,HighProfitabilitySales返回具有高盈利能力的藍色產品的銷售額文虏,即使侣诺,就我們目前所知,它應該返回所有高利潤產品的銷售額氧秘,而不考慮顏色年鸳。要么 DAX沒有替換顏色上的篩選器,要么發(fā)生了更復雜的事情丸相。因為我們已經知道DAX替換了顏色上的篩選器搔确,所以我們需要進行更多的研究來理解正確的計算流程。下面的代碼是度量值的公式::我們對行進行了編號灭忠,以便更容易引用公式的各個部分膳算。

1. CALCULATE (
2. SUM ( Sales[SalesAmount] ),
3. FILTER (
4. Product,
5. Product[Unit Price] >= Product[Unit Cost] * 2
6. )
7. )

第一個函數(shù)是CALCULATE。我們知道CALCULATE作為其第一步更舞,計算篩選器參數(shù)畦幢。在執(zhí)行任何其他操作之前,CALCULATE將從第3行開始計算FILTER表達式缆蝉。

FILTER是一個迭代器宇葱,它遍歷Product表(參見第4行)瘦真。FILTER不會看到所有產品;它只能看到當前篩選上下文中可見的行∈蚯疲現(xiàn)在诸尽,問題是DAX在哪個篩選上下文中計算第4行上的Product ?請記住印颤,CALCULATE仍然沒有創(chuàng)建新的篩選上下文您机。稍后,在評估篩選器之后年局,它將執(zhí)行此操作际看。您可以推斷出CALCULATE中的篩選是在原始篩選上下文下評估的,而不是在CALCULATE創(chuàng)建的篩選上下文下矢否。雖然這似乎是顯而易見的仲闽,但在許多DAX公式中,這種簡單的考慮是錯誤的主要來源之一僵朗。

第4行的Product是指在原始篩選上下文中可見的產品赖欣。對于圖5-9中的藍色行,該上下文僅顯示藍色產品验庙。因此顶吮,F(xiàn)ILTER將迭代藍色產品,它將只返回具有高盈利能力的藍色產品粪薛。然后CALCULATE將刪除現(xiàn)有的顏色篩選器悴了,但這樣的篩選器現(xiàn)在已經包含在FILTER的結果中,從而導致您正在觀察的行為汗菜。重要的是要正確理解篩選的流程让禀,在CALCULATE計算的表達式內替換顏色上的篩選器,而不是在CALCULATE篩選器內部陨界。換句話說巡揍,CALCULATE的篩選器參數(shù)在先前的篩選上下文中進行評估。只有稍后的CALCULATE才會創(chuàng)建新的篩選上下文菌瘪,在該上下文中計算其表達式參數(shù)腮敌。

要獲得完整的圖片,您可以查看以下公式:

[HighProfitabilityALLSales] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
ALL ( Product ),
Product[Unit Price] >= Product[Unit Cost] * 2
))

這一次俏扩,我們使用FILTER(ALL(Product))而不是使用FILTER(Product)糜工。 FILTER不會只迭代藍色產品;它將始終迭代所有產品录淡,并且因為CALCULATE替換顏色上的篩選器捌木,行為如圖5-10所示。

圖5-10 HighProfitabilityALLSales顯示在CALCULATE內有效替換顏色上的過濾器嫉戚。

HighProfitabilityALLSales始終顯示所有高盈利產品的銷售額刨裆,有效地忽略了先前存在的顏色篩選器澈圈。

讓我們從第一個例子開始得出一些結論。

  • 您可以在CALCULATE中使用布爾條件帆啃,但是為了使用它們瞬女,您只能在表達式中引用一列,否則您將收到語法錯誤努潘。
  • 您可以在CALCULATE中使用FILTER或任何其他表函數(shù)作為篩選器參數(shù)诽偷,在這種情況下,表中的所有列都是新篩選器上下文的一部分疯坤。這意味著CALCULATE將替換這些列上的任何現(xiàn)有篩選器报慕。
  • 如果使用FILTER,則CALCULATE將在原始篩選上下文中評估FILTER压怠。另一方面卖子,如果使用布爾條件,則CALCULATE將替換現(xiàn)有的篩選上下文刑峡,但僅替換受影響的列。

5.2.2 用復雜條件進行篩選

當您使用多個篩選器時玄柠,CALCULATE在創(chuàng)建新篩選上下文時會對其所有篩選條件執(zhí)行邏輯AND突梦。因此,如果您想篩選Tailspin Toys生產的所有黑色產品羽利,您可以使用如下表達式:

[Calculate Version] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
Product[Brand] = "Tailspin Toys",
Product[Color] = "Black"
)

因為CALCULATE將這兩個條件放在AND中宫患,您可能會認為表達式的這個公式是等價的:

[FILTER Version] :=
CALCULATE (
SUM ( Sales[SalesAmount]),
FILTER (
Product,
AND (
Product[Brand] = "Tailspin Toys",
Product[Color] = "Black"
)))

實際上,這兩個表達式是不同的这弧,本節(jié)解釋了原因娃闲。您已經了解了這一點,但由于本主題的復雜性以及本節(jié)后面討論的概念的重要性匾浪,值得重復一遍皇帮。在帶有布爾表達式的公式中,品牌和顏色都會忽略現(xiàn)有的篩選上下文蛋辈;而在使用FILTER的公式中属拾,兩列都考慮了之前存在的篩選上下文(在應用公式之前)。

因此冷溶,Calculate Version始終返回黑色Tailspin Toys的銷售額渐白,而FILTER Version只返回在之前存在的篩選上下文中已經存在的銷售額;否則返回一個空值逞频。您可以在圖5-11中觀察到此行為纯衍。

圖5-11兩個公式導致不同的計算,即使它們看起來非常相似苗胀。

不同之處在于襟诸,F(xiàn)ILTER會迭代由外部篩選上下文篩選的表瓦堵。請記住以下公式:

[Sales of Tailspin Toys] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
Product[Brand] = "Tailspin Toys",
)

相當于下一個:

[Sales of Tailspin Toys] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
ALL ( Product[Brand] ),
Product[Brand] = "Tailspin Toys",
))

在第二個公式中,通過使用ALL(Product [Brand])励堡,我們明確要求忽略制造商列的當前篩選上下文谷丸。我們再怎么強調理解這些公式的重要性也不為過。即使這里使用的表達式用于教育目的应结,在編寫自己的表達式時也會遇到類似的情況刨疼,并確保最終會看到奇怪的結果。為了理解公式的行為鹅龄,您必須了解上下文行為揩慕。

在單個列上工作,上述等價可以正常工作扮休。在我們的示例中迎卤,我們有兩列,您可能會嘗試通過以下公式將等效擴展到多列場景:

FilterAll Version :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
ALL ( Product ),
AND (
Product[Brand] = "Tailspin Toys",
Product[Color] = "Black"
)))

此公式不滿足您在本節(jié)中看到的第一個公式所解決的要求玷坠。如果使用ALL(Product)蜗搔,則通過忽略整個表上的篩選上下文來忽略兩列上的篩選上下文。但是八堡,通過忽略整個表上的篩選上下文樟凄,您仍然會得到不同的行為。為了看到效兄渺,我們需要使數(shù)據(jù)透視表更復雜缝龄。在圖5-12中,我們在行上添加了類別名稱挂谍。

圖5-12在Product表上使用FILTER和ALL仍然無法解決方案叔壤。

如您所見,F(xiàn)ilterAll Version忽略整個Product表上的篩選上下文口叙,即使對于Computers類別也顯示該值炼绘,Calculate Version顯示一個空值。原因是Calculate Version僅忽略顏色和品牌名稱的篩選上下文妄田,而FilterAll Version忽略整個表上的篩選上下文饭望,從而忽略該類別。

為了找到正確的公式形庭,你必須用列而不是表來考慮铅辞。我們既不能將Product表提供給FILTER(因為它包含完整的原始篩選上下文),也不能提供ALL(Product)(因為它忽略了所有篩選器)萨醒。相反斟珊,我們需要計算一個Product表,我們在其中刪除了制造商上的篩選器富纸,但其中任何其他現(xiàn)有篩選器仍處于活動狀態(tài)囤踩。我們已經知道了一個函數(shù)旨椒,它允許我們以這種非常精細的方式處理篩選上下文:它是CALCULATE。唯一的問題是CALCULATE需要一個返回單個值的表達式堵漱,這次我們想要返回一個整表综慎,因為我們需要它作為FILTER函數(shù)的參數(shù)。幸運的是勤庐,有一個CALCULATE的伴侶示惊,而不是返回單個值,返回一個表愉镰。它是CALCULATETABLE米罚,將在下一節(jié)介紹。

5.2.3 使用CALCULATETABLE

CALCULATETABLE的工作方式與CALCULATE相同丈探。唯一的區(qū)別在于結果的類型:CALCULATE計算返回標量值录择,而CALCULATETABLE計算表表達式并返回表。下一個公式完全按照我們的需要執(zhí)行:它從Brand和Color中刪除篩選上下文碗降,但讓其他過濾器在FILTER函數(shù)內部流動隘竭。

[CalcTable Version] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
CALCULATETABLE (
Product,
ALL ( Product[Brand] ),
ALL ( Product[Color] )
),
AND (
Product[Brand] = "Tailspin Toys",
Product[Color] = "Black"
)))

如圖5-13所示,CalcTable Version中的最后一個公式計算正確的值讼渊,該值與Calculate Version返回的值相同货裹。

圖5-13使用CALCULATETABLE導致計算正確的值。

這種關于等價結果的題外話很重要精偿,因為了解將布爾篩選器轉換為FILTER等效項的正確方法將極大地幫助您處理更復雜的條件。例如赋兵,如果您想表達OR條件而不是AND條件笔咽,則需要使用此方法。

例如霹期,如果您想計算所有品牌和顏色的總和叶组,其中條件是品牌可以是Tailspin Toys或顏色是黑色(OR條件),那么您需要使用CALCULATETABLE定義它历造,如下面的代碼:

[CalcTable Version OR] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
CALCULATETABLE (
Product,
ALL ( Product[Brand] ),
ALL ( Product[Color] )
),
OR (
Product[Brand] = "Tailspin Toys",
Product[Color] = "Black"
)))

實際上甩十,使用CALCULATETABLE是一種從Brand和Color中刪除篩選器的便捷方法,而不影響其他篩選吭产。因此侣监,使用簡單的CALCULATE可以輕松解決具有多列的AND條件,因為CALCULATE會自動將其所有篩選器參數(shù)放入AND中臣淤。然而橄霉,不同列之間的OR條件要復雜得多,因為您不能依賴CALCOULATE的自動AND邑蒋,并且需要手動編寫復雜的DAX代碼姓蜂。

值得注意的是按厘,作為替代公式,您還可以使用以下代碼钱慢,該代碼使用包含兩列的ALL函數(shù):

[ALL Version OR] :=
CALCULATE (
SUM ( Sales[SalesAmount] ),
FILTER (
ALL ( Product[Brand], Product[Color] ),
OR (
Product[Brand] = "Tailspin Toys",
Product[Color] = "Black"
)))

后一種方法更加優(yōu)雅逮京,即使在開始時它也不是很直觀。

在本節(jié)中束莫,您已經看到懒棉,只要您使用多個列,或者在一般情況下使條件更加復雜麦箍,結果就會變得難以理解漓藕。即使是經驗豐富的DAX程序員也經常發(fā)現(xiàn)很難把握計算流程。因此挟裂,不要被這一節(jié)的復雜性所嚇倒享钞;只有經驗才能引導您走向自然的道路,在那里您將學習如何乍看之下閱讀公式诀蓉。

5.3 理解上下文轉換

我們之前曾預料到CALCULATE會執(zhí)行另一項非常重要的任務:它將任何現(xiàn)有的行上下文轉換為等效的篩選上下文栗竖。

為了演示該行為,您需要創(chuàng)建一個包含CALCULATE表達式的計算列渠啤。計算列始終具有行上下文狐肢,因此這將觸發(fā)上下文轉換。例如沥曹,您在Product中定義包含以下DAX表達式的計算列:

Product[SumOfUnitPrice] = SUM ( Product[Unit Price] )

此公式匯總了所有產品的所有清單價格份名。表達式在行上下文中進行計算,沒有篩選上下文妓美,因此它返回表中所有產品的單位總價格僵腺,而不是計算它的產品的定價。您可以在圖5-14中看到該行為壶栋。

圖5-14在計算列內計算的SumOfUnitPrice返回單位價格的總計辰如。

現(xiàn)在,您可以使用稍微修改的表達式版本創(chuàng)建新的計算列贵试,這次涉及CALCULATE:

Product[SumOfUnitPriceCalc] = CALCULATE ( SUM ( Product[Unit
Price] ) )

什么琉兜?使用單個表達式參數(shù)進行CALCULATE?篩選器在哪里毙玻?無處豌蟋。事實上,我們使用最簡單的CALCULATE形式桑滩。我們之前說過夺饲,CALCULATE的唯一強制性參數(shù)是第一個,所以在沒有任何篩選器的情況下調用CALCULATE是完全可以的。在這種情況下往声,CALCULATE不會更改現(xiàn)有的篩選上下文與其他條件擂找,但它仍然執(zhí)行您現(xiàn)在正在學習的行為:它采用現(xiàn)有的行上下文(如果有)并將它們轉換為等效的篩選上下文。請注意浩销,所有現(xiàn)有的行上下文都會合并到新的篩選上下文中贯涎,我們稍后會詳細介紹。

在該示例中慢洋,CALCULATE搜索現(xiàn)有行上下文塘雳,并發(fā)現(xiàn)Product上有一個,因為CALCULATE在定義計算列中運行普筹。 CALCULATE接受此行上下文并將其替換為僅包含當前由行上下文迭代的行的篩選上下文败明。
我們將此行為稱為上下文轉換。一般來說太防,我們說CALCULATE執(zhí)行上下文轉換妻顶,將所有行上下文合并為一個新的等效篩選上下文。

在CALCULATE內部蜒车,表達式SUM (Product[Unit Price])在只包含當前產品行的篩選上下文中計算其值讳嘱,這是因為CALCULATE執(zhí)行了上下文轉換。這一次的結果是相同的產品單價酿愧,如圖5-15所示沥潭。

圖5-15使用CALCULATE,行上下文已轉換為過濾器上下文嬉挡,從而更改結果钝鸽。

第一次觀察到這種行為時,很難理解為什么CALCULATE執(zhí)行上下文轉換庞钢。在您開始使用此特性后拔恰,您會愛上它,因為您將能夠編寫強大的公式焊夸。

此外,上下文轉換還有另一個非常重要的副作用蓝角。您可能還記得篩選上下文和行上下文在關系中的行為方式不同:行上下文不會自動通過關系傳遞阱穗,而篩選上下文會隨關系傳遞。因此使鹅,當發(fā)生上下文轉換時揪阶,篩選上下文會自動傳遞到相關表。

如果您使用以下定義創(chuàng)建兩個仍在Product中的新計算列患朱,而不是對列表價格求和鲁僚,則可以觀察到此行為:

Product[SalesAmount] = SUM ( Sales[SalesAmount] )
Product[SalesAmountCalc] = CALCULATE ( SUM (
Sales[SalesAmount] ) )

在圖5-16中,您可以看到結果,這顯然是不同的冰沙。

圖5-16 CALCULATE引起的上下文轉換會影響相關表的篩選侨艾。

如您所見,SalesAmount列包含所有銷售的總計拓挥,SalesAmountCalc包含當前產品的銷售額唠梨。 CALCULATE的存在以及Product上行上下文的相關上下文轉換將篩選器傳播到Sales,僅顯示一個產品的銷售額侥啤。

請注意当叭,執(zhí)行CALCULATE時,所有活動的上下文都會發(fā)生上下文轉換盖灸。實際上蚁鳖,不同的表上可能有多個行上下文。例如赁炎,如果在Product中創(chuàng)建的計算列中使用AVERAGEX迭代客戶醉箕,則兩個行上下文(Product和Customer)都會發(fā)生上下文轉換,Sales表將接收這兩個篩選器甘邀。請考慮以下表達式:

Product[SalesWithSUMX] =
AVERAGEX (
Customer,
CALCULATE ( SUM ( Sales[SalesAmount] ) )
)

該公式計算客戶購買此產品所花費的平均金額(不是平均價格琅攘,而是花費的總金額的平均值)。 CALCULATE中的SUM在篩選上下文中執(zhí)行松邪,該篩選上下文僅顯示當前客戶(由AVERAGEX迭代)和當前產品(由計算列評估迭代)的銷售額坞琴。這里有一個簡單的方法來記住這個規(guī)則:在CALCULATE內部沒有行上下文;僅存在篩選上下文逗抑。

5.3.1 理解度量值中的上下文轉換

由于DAX的另一個隱藏方面剧辐,理解上下文轉換非常重要。到目前為止,我們總是使用函數(shù)和列在CALCULATE中編寫表達式剖膳。但是乔煞,您也可以編寫包含度量值調用的表達式。如果從計算列內部調用度量值忍啤,會發(fā)生什么?更一般地說仙辟,如果從行上下文中調用度量值會發(fā)生什么同波?

例如,您可以通過以下方式定義度量值SumOfSalesAmount:

[SumOfSalesAmount] := SUM ( Sales[SalesAmount] )

然后叠国,您可以使用以下更簡單的代碼定義SalesWithSUMX計算列:

Product[SalesWithSUMX] =
SUMX (
Customer,
CALCULATE ( [SumOfSalesAmount] )
)

CALCULATE的存在表明發(fā)生了上下文轉換未檩。問題在于,無論何時從另一個表達式中調用度量值粟焊,DAX都會自動將度量值封裝在CALCULATE中冤狡。因此孙蒙,前一個表達式的行為與以下表達式相同:

Product[SalesWithSUMX] =
SUMX (
Customer,
[SumOfSalesAmount]
)

這次,公式中沒有可見的CALCULATE悲雳,但由于DAX添加了自動CALCULATE挎峦,因此發(fā)生了上下文轉換。

這就是為什么總是編寫區(qū)分列和度量值的代碼非常重要的原因怜奖。 DAX作者使用的實際標準是避免將表名放在度量值前面浑测,并且總是在列的前面加上表名。實際上歪玲,在上一個公式中迁央,SumOfSalesAmount之前缺少表名表明SumOfSalesAmount是一個度量值,因此滥崩,您知道上下文轉換發(fā)生了岖圈。

自動上下文轉換使得編寫通過迭代執(zhí)行復雜計算的公式變得非常容易。話雖如此钙皮,您需要一些時間才能熟悉閱讀和使用它蜂科。例如,如果您想僅計算購買超過總體平均水平的客戶的銷售總額短条,則可以按以下方式編寫度量值:

[SalesMoreThanAverage] :=
VAR
AverageSales = AVERAGEX ( Customer, [SumOfSalesAmount] )
RETURN
SUMX (
Customer,
IF (
[SumOfSalesAmount] > AverageSales,
[SumOfSalesAmount]
))

在前面的代碼中,我們使用SumOfSalesAmount作為不同行上下文中的度量值贡定。在變量定義中可都,我們使用它來計算客戶銷售的平均值,而在使用SUMX的迭代中旋炒,我們使用它來檢查當前客戶的銷售額與之前存儲在變量中的平均值签杈。

警告
基于VAR的語法更易于閱讀和維護(也可能更快)。但是答姥,無論您使用的DAX版本如何铣除,使用不同的語法(也不使用VAR)了解不同的行為非常重要。如果您不理解并掌握自動上下文轉換踢涌,您可能會花費大量令人沮喪的時間來查看公式通孽,而不能完全理解正在計算的值序宦。

調用度量值時會自動執(zhí)行上下文轉換睁壁,并且無法避免它背苦。這意味著在調用度量值時避免上下文轉換的唯一方法是擴展其代碼。例如潘明,假設您以不同的方式編寫了以前的代碼行剂,不是使用變量,而是定義一個名為AverageSales的度量值钳降,它表示客戶的平均銷售額厚宰,如下面的代碼所示:

[AverageSales] := AVERAGEX ( Customer, [SumOfSalesAmount] )
[SalesMoreThanAverage] :=
SUMX (
Customer,
IF (
[SumOfSalesAmount] > [AverageSales],
[SumOfSalesAmount]
))

在突出顯示的行中,您使用[AverageSales]計算客戶的平均銷售額遂填。問題是铲觉,這一次您調用的是迭代(SUMX)中的度量值,這使得上下文轉換發(fā)生吓坚。因此撵幽,[AverageSales]的結果不是所有客戶的平均銷售額,而是僅針對您正在迭代的客戶的平均銷售額礁击。因此哆窿,測試將始終失敗并且度量值返回BLANK强衡,因為IF永遠不會執(zhí)行真分支食侮。如果要避免上下文轉換,則需要擴展度量值的代碼眉尸,如以下示例所示:

[SalesMoreThanAverage] :=
SUMX (
Customer,
IF (
[SumOfSalesAmount] > AVERAGEX ( Customer, [SumOfSalesAmount]
),
[SumOfSalesAmount]
))

用擴展代碼替換了度量值調用后噪猾,現(xiàn)在SalesMoreThanAverage返回正確的結果。此外坪蚁,值得注意的是敏晤,在這種情況下男摧,在Customer上有兩個嵌套的行上下文和三個度量值調用。其中兩個通過SUMX計算當前迭代客戶的銷售額帆离,另一個(AVERAGEX內部)通過AVERAGEX計算當前迭代客戶的銷售額哥谷。

我們將在下一章中廣泛使用這些特性,那時我們將開始編寫復雜的DAX代碼來解決特定的場景监婶。

5.3.2 上下文轉換后多少行可見惑惶?

上下文轉換將行上下文轉換為等價的篩選上下文。

這一聲明需要澄清鱼冀。行上下文始終包含單個行,而上下文轉換后CALCULATE創(chuàng)建的篩選上下文可能包含多行荸型,而CALCULATE創(chuàng)建的篩選器可能會影響一個或多個列瑞妇,具體取決于表結構。

如果表具有在模型中定義的主鍵柳琢,則CALCULATE將創(chuàng)建僅篩選主鍵的篩選上下文。實際上倒堕,這樣的篩選上下文包含由主鍵唯一標識的單個行垦巴。值得記住的是骤宣,可以通過使用表的元數(shù)據(jù)或通過創(chuàng)建將表作為目標的關系來定義主鍵。在這兩種情況下芬膝,上下文轉換都將篩選單個列,因為該列是表的標識锈遥,只有一行所灸。

如果表沒有主鍵,則上下文轉換會在表的所有列上創(chuàng)建一個篩選器侠驯。這可能會導致包含一行或多行的篩選器儒士,具體取決于表內容着撩。實際上,如果所有行都不同薯鳍,則篩選上下文唯一地標識一行。但是壶辜,如果表中有相同的行砸民,則所有這些行都將包含在篩選上下文中。

在以下示例中演侯,Wrong Sales和Correct Sales將返回不同的值:

[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[Unit
Price] )
[Wrong Sales] := SUMX ( Sales, [Sales Amount] )
[Correct Sales] := SUMX ( Sales, Sales[Quantity] * Sales[Unit
Price] )

實際上,Wrong Sales會對銷售進行迭代娄徊,并且對于每一行寄锐,它會計算所有相同行的Sales Amount,而Correct Sales則計算每個單獨行的數(shù)量怠褐。因此奈懒,如果Sales中存在多個相同的行铣猩,則Wrong Sales會產生更高的值天吓。

處理維度表時,這通常不是問題物邑,因為維度總是具有主鍵色解。在這種情況下,唯一與當前行相同的行是行本身锣笨。在事實表或一般沒有主鍵的表上,您需要考慮您可能有重復的行走趋;否則你可能會得到意想不到的結果氮唯。

5.3.3 理解上下文轉換的評估順序

至此惩琉,您已經了解了Product表中創(chuàng)建的這兩個計算列之間的區(qū)別:

Product[SumOfUnitPrice] = CALCULATE ( SUM ( Product[Unit
Price] ) )
Product[SumOfAllUnitPrice] = CALCULATE ( SUM ( Product[Unit
Price] ), ALL ( Product ) )

它們都是計算列,它們都使用CALCULATE函數(shù)伍玖。因此,兩者都發(fā)生了上下文轉換椰棘。

SumOfUnitPrice應僅包含當前行的Unit Price。但是帆卓,SumOfAllUnitPrice的值是多少鳞疲?直觀地說,因為有一個ALL(Product)腺毫,你應該期望該列包含所有單價的總和。這正是它計算的內容急黎。然而淤击,如果您按照我們到目前為止所描述的規(guī)則去做,您會發(fā)現(xiàn)存在錯誤印机。

事實上射赛,ALL(Product)返回整個Product表,有效地從篩選上下文中刪除任何篩選器腐魂。但是,與此同時豁生,上下文轉換應篩選產品并僅顯示一行。如果將這兩個條件放在AND中芍殖,則上下文轉換的限制性更大豌骏,因此它應該獲勝窃躲。那么,為什么結果是所有單價的總和秧秉,而不是當前的單價福贞?

因為上下文轉換創(chuàng)建的篩選上下文與CALCULATE中的條件創(chuàng)建的篩選上下文之間存在優(yōu)先順序。CALCULATE先執(zhí)行上下文轉換拇舀,稍后應用篩選器骄崩。因此,CALCULATE的任何條件都可以覆蓋由上下文轉換創(chuàng)建的篩選器脱惰。

描述這種行為比使用更難。實際上蔚润,上面的兩個公式可以直觀地計算出人們所期望的嫡纠。然而,重要的是要理解為什么會發(fā)生這種情況痴颊,最重要的是锌杀,CALCULATE中的篩選器會覆蓋來自上下文轉換的篩選器(換句話說,篩選器參數(shù)將在后面應用)玉转。

5.4 變量和計算上下文

在第2章“介紹DAX”中猾担,我們展示了變量的概念绑嘹,它是通過使用以下表達式創(chuàng)建的:

VAR Denominator = SUMX ( Sales, Sales[SalesAmount] +
Sales[TotalProductCost] )
VAR Numerator = SUM ( Sales[SalesAmount] )
RETURN
DIVIDE ( Numerator, Denominator )

變量可以方便地使代碼更容易閱讀,并避免多次重復相同的子表達式擅腰,但由于它們使用計算上下文的方式,它們具有另一個非常重要的用途箱歧。事實上洒沦,DAX在您定義它們的計算上下文中計算變量瞒津,而不是在使用變量的上下文中。

這個特性對于復雜的公式非常有用屁柏,您希望根據(jù)以前的計算上下文引用值僧家。讓我們看一個例子。想象一下肌稻,您希望使用數(shù)據(jù)透視表來顯示類別,并為每個類別顯示高銷售產品的數(shù)量旦棉。如果產品的銷售額超過該類別總銷售額的10%,則將產品定義為高銷售額真屯。您可以在圖5-17中看到要實現(xiàn)的結果。

圖5-17此數(shù)據(jù)透視表顯示每個類別中有多少高銷售產品。

使用變量可以非常輕松地計算此度量值。此外羞秤,使用變量也使得閱讀更簡單,如下面的代碼所示:

[HighSalesProducts] :=
VAR
TenPercentOfSales = [SalesAmount] * 0.1
RETURN
COUNTROWS (
FILTER (
Product,
[SalesAmount] >= TenPercentOfSales
))

該公式的有趣部分是DAX計算FILTER迭代之外的變量TenPercentOfSales佩抹。如果TenPercentOfSales的計算是在迭代內部匹摇,那么由于上下文轉換,它將計算當前迭代產品的10%的銷售額廊勃,從而使整個度量值計算失敗懈贺。相反,DAX計算迭代之外的值坡垫,然后在內部使用它梭灿,從而可以引用當前篩選上下文之外的表達式的值。如果你要編寫沒有變量的相同度量值冰悠,你應該編寫一個表達式,如下所示:

[HighSalesProductsCalculate] :=
COUNTROWS (
FILTER (
Product,
[SalesAmount] >= CALCULATE (
[SalesAmount],
ALL ( Product ),
'Product Category'
) * 0.1
))

后一段代碼要難讀得多尉尾,因為基本上故河,您需要在迭代器內部重建先前的計算上下文熏挎,這不是一件容易的事呕乎,即使對于經驗豐富的DAX程序員也是如此湿刽。實際上刃滓,您將僅在第10章中了解前前面表達式的詳細信息,其中我們將揭示篩選上下文的所有詳細信息。

備注:
如果在閱讀上一個示例時哲嘲,您發(fā)現(xiàn)可以使用變量來計算諸如EARLIER等計算上下文之類的內容毫别,那么我們很高興地歡迎您加入那些理解計算上下文的DAX程序員的精英债沮。如果沒有,請不要擔心近顷。在第一次閱讀本章時蓉媳,它永遠不會發(fā)生姨蟋。書中還有很多內容專門介紹這些復雜的主題堂飞,可以幫助您掌握計算上下文所需的技能年叮。

5.5 理解循環(huán)依賴

在設計數(shù)據(jù)模型時终蒂,應該注意一個復雜的主題瀑凝,即公式中的循環(huán)依賴關系冠跷。在本節(jié)中仪糖,您將了解循環(huán)依賴關系以及如何在模型中避免它們结窘。

在談到循環(huán)依賴之前,有必要討論簡單的線性依賴關系。讓我們看一個例子,它包含以下計算列:

Product[Profit] = Product[Unit Price] - Product[Unit Cost]

新計算列取決于同一個表的兩列题翻。在這種情況下陌知,我們說列Profit取決于Unit Price和Unit Cost。然后阶界,您可以使用以下公式創(chuàng)建一個新列,例如ProfitPct:

Product[ProfitPct] = Product[Profit] / Product[Unit Price]

很明顯,ProfitPct取決于利潤和單價灭红。因此芬骄,當DAX計算表中的計算列時蒲牧,它知道它將僅在Profit之后計算ProfitPct挎扰。否則棘脐,它將無法計算ProfitPct公式的有效值碟嘴。

線性依賴關系通常不需要擔心。在內部慈省,DAX使用它來檢測數(shù)據(jù)模型刷新期間計算列的正確計算順序臀防。在包含許多計算列的普通數(shù)據(jù)模型中,計算的依賴性變?yōu)閺碗s的圖边败,同樣袱衷,引擎可以優(yōu)雅地處理這個圖。

循環(huán)依賴是在此圖中出現(xiàn)循環(huán)時發(fā)生的情況笑窜。例如致燥,循環(huán)依賴關系出現(xiàn)的一個明顯情況是,如果您試圖將利潤的定義修改為這個公式:

Product[Profit] := Product[ProfitPct] * Product[Unit Price]

因為ProfitPct依賴于Profit排截,并且在這個新公式中嫌蚤,Profit取決于ProfitPct辐益,DAX拒絕修改公式并顯示錯誤“檢測到循環(huán)依賴”。

到目前為止脱吱,您已經從公式的角度了解了什么是循環(huán)依賴智政。也就是說,在不注意表內容的情況下箱蝠,通過查看表達式檢測到依賴項的存在续捂。然而,使用CALCULATE可以引入更微妙和復雜的依賴類型宦搬。讓我們通過一個示例來展示這個場景牙瓢,從Product的一列子集開始,如圖5-18所示间校。請注意 矾克,在本例中 -,我們僅加載了Product表憔足,從模型中刪除了所有其他表胁附,以便使場景更加明顯。

圖5-18 Product表的這一列子集對于理解循環(huán)依賴關系非常有用四瘫。

我們有興趣了解使用CALCULATE函數(shù)的新計算列的依賴關系列表汉嗽,如下所示:

Product[SumOfUnitPrice] = CALCULATE ( SUM ( Product[Unit
Price] ) )

乍一看,似乎該列僅取決于單價找蜜,因為這是公式中使用的唯一列。然而稳析,我們使用CALCULATE將當前行上下文轉換為篩選上下文洗做。因為我們沒有定義與表的任何關系,并且我們沒有為它設置主鍵彰居,所以當CALCULATE進行上下文轉換時诚纸,它會篩選表的所有列。如果我們擴展CALCULATE調用的含義陈惰,公式實際上是說:對Product表中與ProductKey畦徘,Product Name,Unit Cost和Unit Price具有相同值的所有行的單價的值相加抬闯。
如果您以這種方式閱讀公式井辆,現(xiàn)在很清楚,代碼依賴于Product的所有列溶握,因為新引入的篩選上下文將篩選表的所有列杯缺。您可以在圖5-19中看到結果表。

圖5-19您可以在此處看到帶有SumOfUnitPrice計算列的Product表睡榆。

您可以嘗試在同一個表中使用完全相同的公式定義新的計算列萍肆∨塾埽考慮使用以下公式定義NewSumOfUnitPrice,該公式與前一個公式相同塘揣。

Product[NewSumOfUnitPrice] = CALCULATE ( SUM ( Product[Unit
Price] ) )

令人驚訝的是包雀,在這一點上,DAX提出了一個錯誤亲铡,稱它檢測到循環(huán)依賴馏艾。奇怪的是,它用相同的代碼在公式中檢測到了以前沒有檢測到的循環(huán)依賴關系奴愉。確實發(fā)生了一些變化琅摩,它是表格中的列數(shù)。如果我們能夠將NewSumOfUnitPrice添加到表中锭硼,我們將達到兩個公式具有以下含義的情況:

  • SumOfUnitPrice對Product表中具有相同ProductKey值的所有行的Unit Price值求和房资,Product Name、Unit Cost檀头、Unit Price和NewSumOfUnitPrice轰异。
  • NewSumOfUnitPrice對Product表中具有相同ProductKey、Product Name暑始、Unit Cost搭独、Unit Price和SumOfUnitPrice值的所有行的Unit Price值求和。

與表中的任何其他列一樣廊镜,計算列成為CALCULATE引入的篩選上下文的一部分牙肝,因此,所有計算列都是依賴項列表的一部分嗤朴。如果您閱讀之前的定義配椭,很明顯兩個公式之間存在循環(huán)依賴關系,這就是為什么DAX拒絕允許創(chuàng)建NewSumOfUnitPrice的原因雹姊。

理解這個錯誤并不容易股缸。然而,找到一個解決方案很簡單吱雏,即使不是很直觀敦姻。問題是,如果表沒有主鍵歧杏,則任何包含CALCULATE的計算列(或對任何度量值的調用镰惦,自動添加CALCULATE)都會從表的所有列(包括計算列)創(chuàng)建依賴關系。如果表具有行標識符(數(shù)據(jù)庫術語中的主鍵)得滤,則情況將有所不同陨献。如果表具有充當行標識符的列,則包含CALCULATE函數(shù)的所有列將僅依賴于行標識符懂更,從而將其依賴性列表減少為單個列眨业。

在Product中急膀,ProductKey列可以唯一標識每一行,因為它是主鍵龄捡。為了將Product Key標記為行標識符卓嫂,有兩個選項:

  • 您可以使用ProductKey作為目標列,從任何表創(chuàng)建到Product的關系聘殖。執(zhí)行此操作將確保ProductKey是Product的唯一值晨雳。
  • 您可以使用“表行為”屬性手動設置ProductKey列的行標識符的屬性。

這些操作中的任何一個都告訴DAX引擎該表具有行標識符奸腺。在這種情況下餐禁,您可以定義NewSumOfUnitPrice列以避免循環(huán)依賴,因為使用CALCULATE的兩個計算列將僅依賴于新鍵突照。

5.6 CALCULATE規(guī)則

回顧一下CALCULATE的工作方式很有用帮非。您可以使用這組規(guī)則來測試您對CALCULATE的理解:如果您可以閱讀并理解所有這些規(guī)則,那么您就可以成為真正的DAX大師讹蘑。

  • CALCULATE和CALCULATETABLE是DAX中唯一直接操作篩選上下文的函數(shù)末盔。
  • CALCULATE只有一個必需參數(shù),即要評估的表達式座慰。其他參數(shù)(也稱為篩選器參數(shù))是用于構建新篩選上下文的篩選器陨舱;如果您只想調用CALCULATE來執(zhí)行上下文轉換,則可以省略它們版仔。
  • CALCULATE中的篩選器參數(shù)可以有三種形狀:
    • (a)布爾條件游盲,例如Product [Color] =“White”。
    • (b)一列的值列表邦尊,以單列表的形式背桐,如ALL(產品[顏色])或更復雜的FILTER表達式,如FILTER(ALL(產品[顏色]蝉揍,產品[顏色] = “白色”)。
    • (c)一個表的行列表畦娄,如ALL(產品)或更復雜的FILTER條件又沾,如FILTER(ALL(產品),Product [Color] =“White”)熙卡。
      使用(a)或(b)編寫的條件只能在單個列上運行杖刷。
      使用(c)形狀寫入的條件可以在任意數(shù)量的列上工作。
  • CALCULATE的所有篩選器參數(shù)都是獨立計算的驳癌,然后將它們放在邏輯AND中滑燃。最后,它們用于確定新創(chuàng)建的篩選上下文颓鲜。
  • CALCULATE的篩選器參數(shù)(從第二個參數(shù)開始)在原始篩選上下文中進行計算表窘,它們可以縮小典予、擴展或替換計算的范圍。例如乐严,當使用直接布爾表達式作為參數(shù)時瘤袖,CALCULATE將替換原始篩選上下文,而在傳遞表上使用FILTER的表達式時昂验,CALCULATE會考慮原始篩選上下文捂敌。 CALCULATE的第一個參數(shù)(要評估的表達式)在新創(chuàng)建的篩選上下文中進行計算。
  • CALCULATE的上下文轉換和篩選器參數(shù)之間存在優(yōu)先順序既琴。 CALCULATE在執(zhí)行上下文轉換后應用篩選器占婉。因此,篩選器可以覆蓋由轉換創(chuàng)建的上下文甫恩。

5.7 介紹ALLSELECT

當您希望使用數(shù)據(jù)透視表中的頁面過濾器或切片器選擇作為參數(shù)之一來執(zhí)行計算時逆济,ALLSELECTED是一個非常有用的函數(shù)。例如填物,想象一下纹腌,您想要構建一個報告,如圖5-20所示滞磺。

圖5-20數(shù)據(jù)透視表中顯示的百分比是根據(jù)顯示的總數(shù)計算的升薯,而不是所有顏色的總和。

在此報告中击困,您將顯示當前行占列總數(shù)的百分比涎劈。使這個百分比難以計算的原因是產品顏色既用作切片器(在本例中,我們刪除了一些可用的顏色)阅茶,也用于行蛛枚。如果你運用目前學到的知識,你可以試試這個公式:

[SalesPct] :=
DIVIDE (
[Sales Amount],
CALCULATE (
[Sales Amount],
ALL ( Product[Color] )
))

使用ALL (Product[Color])可以從Color中刪除約束脸哀,并嘗試在列級別上計算總數(shù)蹦浦。不幸的是,ALL刪除了所有約束撞蜂,包括來自行和來自切片器的約束盲镶,從而導致錯誤的百分比。在圖5-21中蝌诡,您可以在數(shù)據(jù)透視表中查看公式的結果溉贿,其中總計不會顯示100%,而是顯示較小的值浦旱。


圖5-21使用ALL計算的百分比不正確宇色,因為它是針對所有顏色的百分比。

這里的問題是你計算所有顏色的分母,即使用戶只在切片器中選擇了一些顏色宣蠕。對于每一行例隆,您計算該行與分母的百分比,該分母大于數(shù)據(jù)透視表中顯示的總數(shù)植影。

這里需要的是一個函數(shù)裳擎,它不返回所有顏色,只返回在原始篩選上下文中選擇的顏色思币,即完整的主表之一鹿响。我們稱這種計算為可視總計,因為它使用的是用戶可見的總計谷饿,而不是完整數(shù)據(jù)模型的總計惶我。這里使用的函數(shù)是ALLSELECTED。如果你這樣寫公式:

[SalesPct] :=
DIVIDE (
[Sales],
CALCULATE (
[Sales],
ALLSELECTED ( Product[Color] )
))

結果將是本節(jié)開頭所示的正確結果博投。

ALLSELECTED僅返回原始篩選上下文中可見的值绸贡,即數(shù)據(jù)透視表之一。換句話說毅哗,ALLSELECTED忽略數(shù)據(jù)透視表的行和列上的篩選器听怕,并考慮用于計算總計的篩選器。

您可以使用三種不同類型的參數(shù)調用ALLSELECTED:

  • 單列 在ALLSELECTED(Product [Color])中虑绵,返回最初選擇的顏色尿瞭。
  • 全表 與在ALLSELECTED ( Product )中一樣,它將對表的所有列執(zhí)行ALLSELECTED操作翅睛,返回最初選擇的所有行错敢。
  • 沒有參數(shù) 您也可以使用沒有參數(shù)的ALLSELECTED()紧显,它將對數(shù)據(jù)模型中的所有表執(zhí)行ALLSELECTED,從而可以計算數(shù)據(jù)透視表的總計汞扎,行和列上沒有篩選器侈沪。

您將主要使用ALLSELECTED以非常動態(tài)的方式計算百分比和比率样悟。在第10章中馒闷,我們將更深入地描述ALLSELECTED拂到,它隱藏了一些復雜性,法挨,我們將在后面討論高級評估上下文時討論這些復雜性骤铃。

5.8 理解 USERELATIONSHIP

CALCULATE提供的另一個功能是在計算表達式期間激活關系。實際上坷剧,正如您所知,數(shù)據(jù)模型可能包含活動關系和非活動關系喊暖。您可能在模型中具有非活動關系惫企,例如,您在兩個表之間存在許多關系,并且只能保留一個活動關系狞尔。

例如丛版,您可能在銷售表中存儲了每個訂單的訂單日期和交貨日期。通常偏序,您希望根據(jù)訂單日期執(zhí)行銷售分析页畦,但是,對于某些特定度量值研儒,您要考慮交貨日期豫缨。在這種情況下,您在Sales和Date之間創(chuàng)建兩個關系:一個基于OrderDateKey端朵,另一個基于DeliveryDateKey好芭。一次只能激活其中一個,因為您通常按訂單日期分析銷售冲呢,所以您保持與OrderDateKey的關系處于激活狀態(tài)舍败,而另一個處于非激活狀態(tài)。然后敬拓,您要創(chuàng)建一個度量值邻薯,該度量值顯示給定日期的交付值,以便將其與訂單值進行比較乘凸。此新度量值(已交付金額)應使用非活動關系計算銷售額厕诡,同時停用與訂單日期的關系。

在這種情況下翰意,您可以將CALCULATE與USERELATIONSHIP關鍵字一起使用木人,如下面的代碼所示:

[Delivered Amount] :=
CALCULATE (
[Sales Amount],
USERELATIONSHIP ( Sales[DeliveryDateKey], Date[DateKey] )
)

DeliveryDateKey和DateKey之間的關系將在[Sales Amount]的計算期間激活,同時冀偶,與OrderDateKey的關系將被停用醒第。在圖5-22中,您可以看到一個數(shù)據(jù)透視表进鸠,顯示基于OrderDateKey的Sales Amount與新的Delivered Amount度量值之間的不同值稠曼。


圖5-22該圖說明了有序銷售和交付銷售之間的差異。

使用USERELATIONSHIP激活關系時客年,您需要了解一個非常重要的方面:在調用表引用時定義關系霞幅,而不是在調用RELATED或其他關系函數(shù)時定義關系。

例如量瓜,如果要計算2007年交付的所有金額司恳,則此公式將不起作用:

[Delivered Amount in 2007] :=
CALCULATE (
[Sales Amount],
FILTER (
Sales,
CALCULATE (
RELATED ( Date[CalendarYear] ),
USERELATIONSHIP ( Sales[DeliveryDateKey], Date[DateKey] )
) = 2007
)
)

原因是FILTER迭代Sales表(調用Sales表),然后計算條件绍傲。在計算條件期間扔傅,它會更改活動關系耍共,然后調用RELATED。但是猎塞,Sales和Date之間的關系已在調用Sales時定義试读,而不是在使用RELATED時定義。

如果要執(zhí)行上一個計算荠耽,則需要按以下方式重寫度量值:

[Delivered Amount in 2007] :=
CALCULATE (
[Sales Amount],
FILTER (
CALCULATETABLE (
Sales,
USERELATIONSHIP( Sales[DeliveryDateKey], Date[DateKey] )
),
RELATED ( Date[Calendar Year] ) = 2007
))

在后一個公式中钩骇,CALCULATE激活所需關系后調用Sales。因此铝量,在FILTER內部調用RELATED會發(fā)生與DeliveryDateKey活動的關系倘屹。

這種行為使得在計算列表達式中使用非默認關系成為一項復雜的操作,因為表的調用隱含在計算列定義中款违,因此您無法控制它唐瀑,并且您無法使用CALCULATE和USERELATIONSHIP更改該行為。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末插爹,一起剝皮案震驚了整個濱河市哄辣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赠尾,老刑警劉巖力穗,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異气嫁,居然都是意外死亡当窗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門寸宵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崖面,“玉大人,你說我怎么就攤上這事梯影∥自保” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵甲棍,是天一觀的道長简识。 經常有香客問我,道長感猛,這世上最難降的妖魔是什么七扰? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮陪白,結果婚禮上颈走,老公的妹妹穿的比我還像新娘。我一直安慰自己咱士,他們只是感情好疫鹊,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布袖瞻。 她就那樣靜靜地躺著,像睡著了一般拆吆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脂矫,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天枣耀,我揣著相機與錄音,去河邊找鬼庭再。 笑死捞奕,一個胖子當著我的面吹牛,可吹牛的內容都是我干的拄轻。 我是一名探鬼主播颅围,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恨搓!你這毒婦竟也來了院促?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤斧抱,失蹤者是張志新(化名)和其女友劉穎常拓,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辉浦,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡弄抬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宪郊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掂恕。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弛槐,靈堂內的尸體忽然破棺而出懊亡,到底是詐尸還是另有隱情,我是刑警寧澤丐黄,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布斋配,位于F島的核電站,受9級特大地震影響灌闺,放射性物質發(fā)生泄漏艰争。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一桂对、第九天 我趴在偏房一處隱蔽的房頂上張望甩卓。 院中可真熱鬧,春花似錦蕉斜、人聲如沸逾柿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽机错。三九已至爬范,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弱匪,已是汗流浹背青瀑。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留萧诫,地道東北人斥难。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像帘饶,于是被迫代替她去往敵國和親哑诊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容