【DAX圣經(jīng)】第四章:理解計算上下文(1)

到本書的這一章時,您已經(jīng)學習了DAX語言的基礎(chǔ)知識。您知道如何創(chuàng)建計算列和度量值,并且您已經(jīng)很好地理解了DAX中的通用函數(shù)。在這一章中,你將進入該語言的下一階段:在學習了DAX語言堅實的理論背景之后,你將能夠成為一個真正的DAX選手。

到目前為止,您已經(jīng)獲得了一些知識,您可以創(chuàng)建許多有趣的報告,為了能創(chuàng)建更復雜的報告,你還需要學習計算上下文。計算上下文是DAX所有高級特性的基礎(chǔ)。

我們想對我們的讀者說幾句話:計算上下文的概念很簡單,你很快就會學習和理解它。然而,您需要徹底地理解一些底層的細枝末節(jié);否則,在你的DAX學習路徑中,你會感到迷失。我們在公共和私人課程中教許多用戶。我們知道這是絕對正常的。在某一時刻,你會覺得公式像魔術(shù)一樣工作,因為它們是有效的,但你不明白為什么。別擔心:許多人也有同樣的問題。大多數(shù)DAX的學生在學習時能理解,而其他許多學生將來也會理解。這僅僅意味著計算上下文對他們來說不夠清晰。在這一點上,解決方案很簡單:回到這一章,再讀一遍,你可能會發(fā)現(xiàn)一些新的東西,這是你在第一次讀到的時候漏掉了。

此外,計算上下文在CALCULATE函數(shù)的使用中起著重要的作用。CALCULATE可能是DAX中最強大、最難以學習的函數(shù),我們將在第5章“理解 CALCULATE和CALCULATETABLE”中介紹CALCULATE,然后在書的其余部分中使用它。在沒有堅實的計算上下文背景的情況下,理解CALCULATE是有問題的。另一方面,在不嘗試使用CALCULATE的情況下,理解計算上下文的重要性幾乎是不可能的。因此,這一章和隨后的一章根據(jù)我們之前的經(jīng)驗,總是會被標記并反復查閱。

介紹計算上下文

讓我們先了解一下計算上下文是什么。任何DAX表達式都是在上下文中求值的。上下文是計算公式的“環(huán)境”。例如,考慮一個非常簡單的公式:

[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[UnitPrice] )

您已經(jīng)知道這個公式計算的是什么:數(shù)量乘以價格的總和。您可以將這個度量值放在一個透視表中,并查看結(jié)果,如圖4-1所示。

圖4-1 沒有篩選條件的銷售金額度量值,顯示了銷售總額

這個數(shù)字本身看起來一點都不有趣,是嗎?但是,如果你仔細想想,這個公式計算出它應該計算的東西:所有銷售金額的總和,這是一個沒有多少含義的大數(shù)字。當我們使用一些列來分開查看這個結(jié)果時,這個透視表變得就有趣了。例如,您可以使用產(chǎn)品顏色,將其放在透視表行中,這時透視表突然就顯示了一些有趣的業(yè)務見解,如圖4-2所示

圖4-2 按color分割銷售額,看起來更有趣

總數(shù)仍然在那里,但現(xiàn)在它是小數(shù)字的總和,每個值,連同所有其他的值,都有意義。但是,如果你再仔細想想,你應該注意到這里發(fā)生了一些奇怪的事情:這個公式并不是計算我們所要求的。

我們認為這個公式的意思是“所有銷售金額的總和”。但在透視表的每個單元格中,公式并不是計算所有銷售的總和,而是計算具有特定顏色的產(chǎn)品的銷售總額。然而,我們從未指定計算必須在數(shù)據(jù)模型的一個子集上工作。換句話說,這個公式并沒有指定它可以在數(shù)據(jù)子集上工作。

為什么在不同的單元格中,公式計算不同的值?答案是非常簡單的:因為在這個計算上下文下,DAX計算公式。你可以把一個公式的計算上下文想象成圍繞在單元格周圍可以計算DAX公式的區(qū)域。

因為產(chǎn)品的顏色在各行中,透視表中的每一行都可以看到,在整個數(shù)據(jù)庫中,只有特定顏色的產(chǎn)品的子集。這是公式的周圍區(qū)域,也就是在公式計算之前應用到數(shù)據(jù)庫的一組過濾器。當公式計算所有銷售金額的總和時,它不會在整個數(shù)據(jù)庫中計算它,因為它無法選擇全部行。當DAX計算出與行值為白色的公式時,只有白色的產(chǎn)品是可見的,因此,它只考慮與白色產(chǎn)品相關(guān)的銷售。因此,當計算僅顯示白色產(chǎn)品的透視表中的一行時,銷售金額的總和就變成了所有白色產(chǎn)品的銷售額之和。

任何DAX公式都確定了一個計算,但是DAX在一個上下文執(zhí)行計算,它定義了計算的最終值。公式雖然是一樣的,但是值是不同的因為DAX根據(jù)不同的數(shù)據(jù)子集對它進行計算。

現(xiàn)在讓我們把年份放在列上,使透視表更有趣。現(xiàn)在的報告如圖4-3所示。

圖4-3 銷售額現(xiàn)在被顏色和年份分割

計算的規(guī)則在這一點上應該是清楚的:每個單元格現(xiàn)在都有不同的值,即使公式總是相同的,因為透視表的行和列選擇都定義了上下文。事實上,2008年的白色產(chǎn)品銷售與2007年的白色產(chǎn)品銷售不同。而且,因為您可以在行和列中放置多個字段,所以最好說行上的字段集和列上的字段集定義上下文。圖4-4使這個更加明顯

圖4-4 上下文是由行和列上的字段集定義的

每個單元格有不同的值,因為在行、顏色和品牌名稱上有兩個字段。行和列完整的字段集合定義了上下文。例如,圖4-4中突出顯示的單元格的上下文對應黑色、品牌Contoso和日歷年。

注意
字段是否在行或列上(或在切片器和/或頁面過濾器上,或者在任何其他您可以用查詢創(chuàng)建的類型過濾器中)并不重要。所有這些過濾器共同定義單個上下文,而DAX利用這個上下文來計算公式。在行或列上放置一個字段會產(chǎn)生一些美觀上的影響,但是在DAX計算值的方式上沒有任何變化。

現(xiàn)在讓我們看完整的場景。在圖4-5中,我們在切片器上添加了產(chǎn)品類別,并在過濾器上添加了月份,我們選擇了12月。

圖4-5 在一個典型的報告中,上下文定義在很多方式,包括切片器和過濾器

在這一點上,很明顯,每個單元計算的值都有一個由行、列、切片器和過濾器定義的上下文。所有這些過濾器共同定義一個上下文,并且在公式計算之前,DAX將該上下文應用于數(shù)據(jù)模型上。此外,重要的是要知道,并非所有的單元格都具有相同的過濾器集,不僅在值方面,而且在字段方面也是如此。例如,列上的grand total只包含類別、月份和年份的過濾器,但是它不包含顏色和品牌的過濾器。顏色和品牌的字段在行中,它們不會過濾總數(shù)。這同樣適用于透視表中顏色的子總數(shù):對于那些單元,制造商沒有過濾器,來自行的唯一有效的過濾器是顏色

我們把這個上下文稱為篩選上下文,正如它的名字所暗示的那樣,它是一個篩選表的上下文。您所編寫的任何公式都有不同的值,這取決于執(zhí)行計算的DAX語句上的篩選上下文。這種行為,雖然非常直觀,但需要被充分理解。

現(xiàn)在您已經(jīng)了解了篩選上下文是什么,您知道下面的DAX表達式應該被理解為“在當前篩選上下文中可見的所有銷售金額的總和”:

[Sales Amount] := SUMX ( Sales, Sales[Quantity] * Sales[UnitPrice] )

稍后您將學習如何閱讀、修改和清除篩選上下文。到目前為止,我們已經(jīng)足夠深入地了解了這個事實,即篩選上下文總是存在于透視表的任何單元格或報告/查詢中的任何值。你總是需要考慮篩選上下文來理解DAX是如何計算公式的。

理解行上下文

篩選上下文是DAX中存在的兩個上下文之一。它的搭檔是行上下文,在本節(jié)中,您將了解它是什么以及它是如何工作的。

這一次,我們使用了一個不同的公式來考慮:

Sales[GrossMargin] = Sales[SalesAmount] - Sales[TotalCost]

您可能會在一個計算列中寫出這樣的表達式,以便計算毛利潤。一旦您在計算列中定義了這個公式,您就會得到結(jié)果表,如圖4-6所示。

圖4-6

DAX對表中所有行進行公式計算,對于每一行,它都按照預期計算出了不同的值。為了理解行上下文,我們需要有點學究式的閱讀公式:我們要求減去兩列,但是我們從哪里告訴DAX從哪一行來得到列的值呢?您可能會說,要使用的行是隱式的。因為它是一個計算列,所以DAX計算它一行一行,對于每一行,它都會計算一個不同的結(jié)果。這是正確的,但是,從DAX表達式的角度來看,要使用哪一行的信息仍然缺失。

實際上,用于執(zhí)行計算的行并沒有存儲在公式中。它是由另一種上下文定義的:行上下文。當你定義計算的列時,DAX從表的第一行開始了一個迭代;它創(chuàng)建了包含該行的行上下文并對表達式求值。然后它移到第二行,再次求出表達式。這發(fā)生在表格中所有的行中,如果你有100萬行,你可以認為DAX創(chuàng)建了100萬行上下文來評估這個公式100萬次。顯然,為了優(yōu)化計算,這并不是發(fā)生的事情;否則,DAX將是一種非常緩慢的語言。不管怎樣,從邏輯的角度來看,這就是它的工作原理。

讓我們試著更精確一點。行上下文是一個總是包含一行的上下文,DAX在計算列的創(chuàng)建過程中自動定義它。您可以使用其他技術(shù)創(chuàng)建行上下文,這將在本章后面討論,但是解釋行上下文的最簡單方法是查看計算過的列,其中引擎總是自動創(chuàng)建它。

總是有兩種上下文
到目前為止,您已經(jīng)了解了行上下文和篩選上下文是什么,它們是DAX中僅有的上下文類型。因此,它們是修改公式結(jié)果的唯一方法。任何公式都將在這兩個不同的上下文中進行計算:行上下文和篩選上下文。

我們將這兩種上下文稱為“計算上下文”,因為它們是改變公式計算方式的上下文,為相同的公式提供不同的結(jié)果。

這是一個非常重要的點,在開始時很難集中注意力:總是有兩個上下文,一個公式的結(jié)果取決于兩者。在你的DAX學習路徑的這一點上,你可能會認為這是顯而易見的,而且是很自然的。你也許是對的。然而,在書的后面,如果你不記得這兩個上下文的共存,你會發(fā)現(xiàn)一些公式是很難理解的,每一個都可以改變公式的結(jié)果。

測試你的計算上下文理解

在我們繼續(xù)進行關(guān)于計算上下文的更復雜的討論之前,我們希望通過幾個示例來測試您對上下文的理解。請不要立即看這個解釋;在這個問題之后停下來,試著回答這個問題。然后閱讀解釋,并試著去理解它。

在計算列中使用SUM

第一個測試是非常簡單的。如果您在銷售表中定義了一個計算列,那么會發(fā)生什么呢?

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

因為它是一個計算列,它將被逐行計算,對于每一行,您將獲得一個結(jié)果。你希望看到什么數(shù)字?從這些選項中選擇一個:

  • 這一行的銷售額的值,同時也就是每一行的不同值。

  • 所有行的總銷售額,也就是說,所有行的值都是一樣的。

  • 一個錯誤;你不能在計算的列中使用求和。

請停止閱讀,當我們等待你的有根據(jù)的猜測之后再繼續(xù)。

現(xiàn)在,讓我們詳細說明當DAX計算公式時發(fā)生了什么。您已經(jīng)了解了公式的含義:“在當前篩選上下文中看到的所有銷售金額的總和?!耙驗樵谝粋€計算列中,DAX會逐行計算公式。因此,它為第一行創(chuàng)建一行上下文,然后調(diào)用公式計算,并對整個表進行迭代。這個公式計算當前篩選上下文中所有銷售金額的總和,所以真正的問題是“當前的篩選上下文是什么?”答案:它是完整的數(shù)據(jù)庫,因為DAX計算這個公式在任何透視表或任何其他類型的篩選條件之外。實際上,當沒有篩選條件激活時,DAX將它作為計算列定義的一部分進行處理。

即使有行上下文,SUM也會忽略它。相反,它使用篩選上下文,現(xiàn)在的篩選上下文是完整的數(shù)據(jù)庫。因此,第二種選擇是正確的:您將獲得總銷售額,與所有銷售行相同的值,如圖4-7所示。

圖4-7 SUM ( Sales[SalesAmount] )在一個計算列中,是根據(jù)完整的數(shù)據(jù)庫計算的

這個例子說明了這兩個上下文共存。他們共同在公式的結(jié)果上工作,但是用不同的方式。計算列中使用的SUM、MIN和MAX等聚合函數(shù)只使用過濾上下文,忽略行上下文,DAX只使用它來確定列值。如果你選擇了第一個答案,就像許多學生通常做的那樣,這是完全正常的。關(guān)鍵是,您還沒有想到這兩個上下文正在一起工作,以不同的方式改變公式結(jié)果。當使用直觀的邏輯時,第一個答案是最常見的,但它是錯誤的,現(xiàn)在你知道為什么了。

在度量值中使用列

我們想和你們做的第二個測試有點不同。假設(shè)你想要在一個度量中定義毛利潤的公式,而不是在一個計算的列中。你有一個帶有銷售金額的列,另一個用于產(chǎn)品成本的列,你可以寫下面的表達式:

[GrossMargin] := Sales[SalesAmount] - Sales[ProductCost]

如果你試圖定義這樣一個度量值,你應該期待什么結(jié)果?

  • 1、這個表達式工作正常,我們需要在報告中測試結(jié)果。

  • 2、一個錯誤,你甚至不能寫這個公式

  • 3。你可以定義這個公式,但是當它在透視表或查詢中使用時,它會給出一個錯誤

和以前一樣,停止閱讀,思考答案,然后閱讀下面的解釋

在這個公式中,我們使用了Sales[SalesAmount],這是一個列名,也就是銷售表中銷售金額的值。這個定義缺少什么嗎?您應該回憶一下,從以前的觀點來看,這里缺少的信息是從哪里獲取當前銷售額的值。當您在計算列中編寫這段代碼時,DAX知道在計算表達式時要使用的行,這要歸功于行上下文。然而,這個度量值會發(fā)生什么呢?沒有迭代,也沒有當前行,也就是說,沒有行上下文。

因此,第二個答案是正確的,你甚至不能寫公式;它在語法上是錯誤的。當你試圖輸入它的時候,你會收到一個錯誤

記住,列本身沒有值。相反,它對于表格的每一行都有不同的值。因此,如果你想要一個單獨的值,你需要指定要使用的行。指定要使用的行的惟一方法是行上下文。因為在這個度量中沒有行上下文,公式是不正確的,從而DAX會拒絕它。

在一個度量值中指定這個計算的正確方法是使用聚合函數(shù),如下所列

[GrossMargin] := SUM ( Sales[SalesAmount] ) - SUM ( Sales[ProductCost] )

使用這個公式時,您現(xiàn)在要求通過SUM進行聚合。因此,后一個公式并不依賴于行上下文;它只需要一個篩選上下文,同時提供了正確的結(jié)果。

利用迭代器創(chuàng)造一個行上下文

您了解到DAX在定義一個計算列時自動創(chuàng)建行上下文。在這種情況下,引擎會逐行計算DAX的表達式?,F(xiàn)在,是時候?qū)W習如何使用迭代器在DAX表達式中創(chuàng)建行上下文了

您可能還記得,在第2章“引入DAX”中,所有以x結(jié)尾的函數(shù)都是迭代器,也就是說,它們遍歷表,并對每一行進行評估,最后用不同的算法聚合結(jié)果。例如,看看下面的DAX表達式:

[IncreasedSales] := SUMX ( Sales, Sales[SalesAmount] * 1.1 )

SUMX是一個迭代器,它迭代銷售表,對于表的每一行,它計算銷售額增加10%到它的值,最后返回所有這些值的總和。為了計算每一行的表達式,SUMX在Sales表上創(chuàng)建行上下文,并在迭代期間使用它。DAX在包含當前迭代行的行上下文中計算內(nèi)部表達式(SUMX的第二個參數(shù))

需要注意的是,在完整的計算流程中,SUMX的不同參數(shù)使用不同的上下文。讓我們更仔細地看一下相同的表達式

= SUMX (
 Sales, ← 外部上下文
 Sales[SalesAmount] * 1.1 ← 外部上下文 + 新的行上下文
)

第一個參數(shù),Sales,是使用來自調(diào)用者的上下文來計算的(例如,它可能是一個透視表單元格,另一個度量值,或者查詢的一部分),而第二個參數(shù)(表達式)是使用外部上下文加上新創(chuàng)建的行上下文來計算的。

所有迭代器的行為都是一樣的:

1、為作為第一個參數(shù)接收的表的每一行創(chuàng)建一個新的行上下文

2、在新創(chuàng)建的行上下文中(加上在迭代開始之前存在的任何其他上下文),對表的每一行進行評估。

3、聚合在步驟2中計算的值。

重要的是要記住,原始上下文在表達式中仍然有效:迭代器只添加新的行上下文;它們不會以任何方式修改現(xiàn)有的。這條規(guī)則通常是有效的,但是有一個重要的例外:如果前面的上下文已經(jīng)包含了同一個表的行上下文,那么新創(chuàng)建的行會上下文隱藏了先前存在的行上下文。我們將在下一節(jié)中更詳細地討論這個問題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容