在迭代函數(shù)中使用行上下文
您了解到,無(wú)論何時(shí)定義計(jì)算列或使用X函數(shù)開(kāi)始迭代時(shí),DAX都會(huì)創(chuàng)建行上下文。當(dāng)我們使用計(jì)算列時(shí),行上下文的存在很容易使用和理解。實(shí)際上,我們甚至可以在不知道行上下文存在的情況下創(chuàng)建簡(jiǎn)單的計(jì)算列。原因是行上下文是由引擎自動(dòng)創(chuàng)建的。因此,我們不必?fù)?dān)心行上下文的存在。另一方面,在使用迭代函數(shù)時(shí),我們負(fù)責(zé)創(chuàng)建和處理行上下文。此外,通過(guò)使用迭代函數(shù),我們可以創(chuàng)建多個(gè)嵌套行上下文;這增加了代碼的復(fù)雜性。因此,重要的是要更準(zhǔn)確地了解帶有迭代函數(shù)的行上下文的行為。
例如,查看以下DAX度量值:
IncreasedSales := SUMX ( Sales, Sales[Net Price] * 1.1 )
由于 SUMX 是迭代函數(shù),因此 SUMX 在 Sales 表上創(chuàng)建一個(gè)行上下文,并在迭代過(guò)程中使用它。行上下文迭代 Sales 表(第一個(gè)參數(shù)),并在迭代過(guò)程中將當(dāng)前行提供給第二個(gè)參數(shù)。換句話說(shuō),DAX在包含第一個(gè)參數(shù)上當(dāng)前迭代的行的行上下文中計(jì)算內(nèi)部表達(dá)式( SUMX 的第二個(gè)參數(shù))。
請(qǐng)注意, SUMX 的兩個(gè)參數(shù)使用不同的上下文。實(shí)際上,任何DAX代碼段都可以在調(diào)用它的上下文中工作。因此,執(zhí)行表達(dá)式時(shí),可能已經(jīng)有一個(gè)過(guò)濾器上下文和一個(gè)或多個(gè)行上下文處于活動(dòng)狀態(tài)。查看帶有注釋的相同表達(dá)式:
SUMX (
Sales, -- External filter and row contexts
Sales[Net Price] * 1.1 -- External filter and row contexts + new row context
)
使用來(lái)自調(diào)用方的上下文評(píng)估第一個(gè)參數(shù)Sales。使用外部上下文加上新創(chuàng)建的行上下文來(lái)評(píng)估第二個(gè)參數(shù)(表達(dá)式)。
所有迭代函數(shù)的行為均相同:
- 在現(xiàn)有上下文中評(píng)估第一個(gè)參數(shù),以確定要掃描的行。
- 為在上一步中評(píng)估的表的每一行創(chuàng)建一個(gè)新的行上下文。
- 迭代表并在現(xiàn)有評(píng)估上下文中評(píng)估第二個(gè)參數(shù),包括新創(chuàng)建的行上下文。
- 匯總上一步中計(jì)算的值。
請(qǐng)注意,原始上下文在表達(dá)式內(nèi)仍然有效。迭代函數(shù)添加新的行上下文;它們不會(huì)修改現(xiàn)有的篩選上下文。例如,如果外部篩選上下文包含針對(duì)紅色的篩選,則該篩選在整個(gè)迭代過(guò)程中仍處于活動(dòng)狀態(tài)。此外,請(qǐng)記住,行上下文是迭代的,它不會(huì)篩選。因此,無(wú)論如何,我們不能使用迭代器覆蓋外部篩選上下文。
該規(guī)則始終有效,但是有一個(gè)重要的細(xì)節(jié)并不重要。如果先前的上下文已經(jīng)包含同一表的行上下文,則新創(chuàng)建的行上下文將在同一表上隱藏先前的現(xiàn)有行上下文。對(duì)于DAX新手,這可能是錯(cuò)誤的來(lái)源。因此,我們將在接下來(lái)的兩節(jié)中詳細(xì)討論行上下文隱藏。
不同表上的嵌套行上下文
由迭代函數(shù)求值的表達(dá)式可能非常復(fù)雜。而且,表達(dá)式可以單獨(dú)包含其他迭代。乍一看,在另一個(gè)迭代中開(kāi)始一個(gè)迭代可能看起來(lái)很奇怪。不過(guò),這是DAX的一種常見(jiàn)做法,因?yàn)榍短椎鲿?huì)生成功能強(qiáng)大的表達(dá)式。
例如,以下代碼包含三個(gè)嵌套的迭代函數(shù),并掃描三個(gè)表:Categories 類別,Products 產(chǎn)品和 Sales 銷售。
SUMX (
'Product Category', -- Scans the Product Category table
SUMX ( -- For each category
RELATEDTABLE ( 'Product' ), -- Scans the category products
SUMX ( -- For each product
RELATEDTABLE ( Sales ) -- Scans the sales of that product
Sales[Quantity] --
* 'Product'[Unit Price] -- Computes the sales amount of that sale
* 'Product Category'[Discount]
)
)
)
最里面的表達(dá)式(三個(gè)因子的乘積)引用了三個(gè)表。實(shí)際上,在該表達(dá)式求值期間打開(kāi)了三行上下文:當(dāng)前正在迭代的三個(gè)表中的每一個(gè)都一個(gè)。還值得注意的是,兩個(gè) RELATEDTABLE 函數(shù)從當(dāng)前行上下文開(kāi)始返回相關(guān)表的行。因此,在 Categories 表的行上下文中執(zhí)行的 RELATEDTABLE(Product)返回給定類別的產(chǎn)品。相同的推理適用于RELATEDTABLE(Sales),該函數(shù)返回給定產(chǎn)品的銷售額。
先前的代碼在性能和可讀性方面都不理想。通常,如果要掃描的行數(shù)不是太大,則嵌套迭代函數(shù)是很好的:數(shù)百行是好消息,數(shù)千行是好消息,數(shù)百萬(wàn)行是壞消息。否則,我們很容易遇到性能問(wèn)題。我們使用前面的代碼演示了可以創(chuàng)建多個(gè)嵌套行上下文。我們將在本書后面的部分中看到更多有用的嵌套迭代函數(shù)示例。通過(guò)使用以下代碼,可以依靠一種單獨(dú)的行上下文和RELATED函數(shù),以一種更快,更易讀的方式表示相同的計(jì)算:
SUMX (
Sales,
Sales[Quantity]
* RELATED ( 'Product'[Unit Price] )
* RELATED ( 'Product Category'[Discount] )
)
只要不同表上有多個(gè)行上下文,就可以使用它們?cè)趩蝹€(gè)DAX表達(dá)式中引用迭代表。但是,有一種情況證明具有挑戰(zhàn)性。那就是當(dāng)我們?cè)谕粡埍砩锨短锥鄠€(gè)行上下文時(shí),這是下一節(jié)所討論的主題。
同一表上的嵌套行上下文
在同一表上嵌套行上下文的場(chǎng)景似乎很少見(jiàn)。但是,它確實(shí)經(jīng)常發(fā)生,并且在計(jì)算列中更頻繁地發(fā)生。假設(shè)我們要根據(jù)標(biāo)價(jià)對(duì)產(chǎn)品進(jìn)行排名。最昂貴的產(chǎn)品應(yīng)排在第1位,第二貴的產(chǎn)品應(yīng)排在第2位,依此類推。我們可以使用RANKX函數(shù)解決該方案。但是出于教育目的,我們展示了如何使用更簡(jiǎn)單的DAX函數(shù)來(lái)解決它。
為了計(jì)算排名,對(duì)于每種產(chǎn)品,我們可以計(jì)算價(jià)格高于當(dāng)前產(chǎn)品的產(chǎn)品數(shù)量。如果沒(méi)有價(jià)格高于當(dāng)前產(chǎn)品價(jià)格的產(chǎn)品,那么當(dāng)前產(chǎn)品是最昂貴的,其排名為1。如果只有一種價(jià)格更高的產(chǎn)品,則排名為2。實(shí)際上,我們正在通過(guò)計(jì)算價(jià)格較高的產(chǎn)品數(shù)量并將結(jié)果加1來(lái)計(jì)算產(chǎn)品的排名。 因此,可以使用此代碼編寫計(jì)算列,其中我們使用 PriceOfCurrentProduct 作為占位符以指示當(dāng)前產(chǎn)品的價(jià)格。
'Product'[UnitPriceRank] =
COUNTROWS (
FILTER (
'Product',
'Product'[Unit Price] > PriceOfCurrentProduct
)
) + 1
FILTER 返回價(jià)格高于當(dāng)前產(chǎn)品價(jià)格的產(chǎn)品,COUNTROWS 對(duì) FILTER 結(jié)果的行進(jìn)行計(jì)數(shù)。唯一剩下的問(wèn)題是找到一種方法來(lái)表達(dá)當(dāng)前產(chǎn)品的價(jià)格,用有效的DAX語(yǔ)法替換 PriceOfCurrentProduct ?!爱?dāng)前”是指DAX計(jì)算列時(shí)當(dāng)前行中列的值。這比您預(yù)期的要難。
將注意力集中在先前代碼的第5行上。此處,對(duì) Product [Unit Price] 的引用是指當(dāng)前行上下文中的 Unit Price 的值。DAX執(zhí)行第5行時(shí)活動(dòng)的行上下文是什么?有兩行上下文。因?yàn)榇a是寫在計(jì)算所得的列中的,所以引擎會(huì)自動(dòng)創(chuàng)建一個(gè)默認(rèn)的行上下文,該上下文會(huì)掃描 Product 表。此外,由于 FILTER 是一個(gè)迭代器,因此由 FILTER 生成的行上下文將再次掃描 Product。如圖4-9所示。

外部框包括正在對(duì) Product 進(jìn)行迭代的計(jì)算列的行上下文。但是,內(nèi)部框顯示 FILTER 函數(shù)的行上下文,該行上下文也遍歷 Product 。表達(dá)式 Product [Unit Price] 取決于上下文。因此,在內(nèi)部框中對(duì) Product [Unit Price] 的引用只能引用 FILTER 當(dāng)前迭代的行。問(wèn)題在于,在該框中,我們需要評(píng)估由計(jì)算列的行上下文所引用的單價(jià)的值,該值現(xiàn)已隱藏。
確實(shí),當(dāng)不使用迭代函數(shù)創(chuàng)建新的行上下文時(shí),Product [Unit Price] 的值就是所需的值,它是所計(jì)算列的當(dāng)前行上下文中的值,如以下簡(jiǎn)單代碼所示:
Product[Test] = Product[Unit Price]
為了進(jìn)一步說(shuō)明這一點(diǎn),讓我們?cè)趦蓚€(gè)框中使用一些偽代碼評(píng)估 Product [Unit Price] 。結(jié)果是不同的結(jié)果,如圖4-10所示,我們?cè)?COUNTROWS 之前添加了對(duì) Product [Unit Price] 的評(píng)估,僅出于教育目的。

這是到目前為止的情況的回顧:
- 由 FILTER 生成的內(nèi)部行上下文隱藏了外部行上下文。
- 我們需要將內(nèi)部 Product [Unit Price] 與外部 Product [Unit Price] 的值進(jìn)行比較。
- 如果我們?cè)趦?nèi)部表達(dá)式中編寫比較,則無(wú)法訪問(wèn)外部 Product [Unit Price] 。
因?yàn)槲覀兛梢詸z索當(dāng)前單價(jià),所以如果在 FILTER 的行上下文之外進(jìn)行評(píng)估,則解決此問(wèn)題的最佳方法是將 Product [Unit Price] 的值保存在變量中。確實(shí),可以使用以下代碼在計(jì)算列的行上下文中評(píng)估變量:
'Product'[UnitPriceRank] =
VAR
PriceOfCurrentProduct = 'Product'[Unit Price]
RETURN
COUNTROWS (
FILTER (
'Product',
'Product'[Unit Price] > PriceOfCurrentProduct
)
) + 1
此外,最好通過(guò)使用更多變量來(lái)分隔計(jì)算的不同步驟,以更具描述性的方式編寫代碼。這樣,代碼也更容易遵循:
'Product'[UnitPriceRank] =
VAR PriceOfCurrentProduct = 'Product'[Unit Price]
VAR MoreExpensiveProducts =
FILTER (
'Product',
'Product'[Unit Price] > PriceOfCurrentProduct
)
RETURN
COUNTROWS ( MoreExpensiveProducts ) + 1
圖4-11顯示了后一種代碼形式的行上下文的圖形表示,它使您更容易理解DAX在哪個(gè)行上下文中計(jì)算公式的每個(gè)部分。

圖4-12顯示了此計(jì)算列的結(jié)果。

因?yàn)橛?4個(gè)產(chǎn)品的單價(jià)相同,所以它們的排名始終為1;第15個(gè)產(chǎn)品的排名為15,與其他價(jià)格相同的產(chǎn)品共享。如果我們能像圖中那樣將1、2、3而不是1、15、19排好,那將是很好的。我們將盡快解決此問(wèn)題,但在此之前,重要的是要先說(shuō)個(gè)小題外話。
為了解決提出類似的問(wèn)題,必須對(duì)行上下文有扎實(shí)的了解,以便能夠檢測(cè)到公式的不同部分中哪個(gè)行上下文是活動(dòng)的,并且最重要的是,要了解行上下文如何影響DAX表達(dá)式返回的值。值得強(qiáng)調(diào)的是,在公式的兩個(gè)不同部分中求值的同一表達(dá)式 Product [Unit Price] 返回不同的值,這是因?yàn)橐獙?duì)其求值的上下文不同。當(dāng)人們對(duì)評(píng)估上下文缺乏扎實(shí)的了解時(shí),處理這樣復(fù)雜的代碼將非常困難。
如您所見(jiàn),具有兩個(gè)行上下文的簡(jiǎn)單排名表達(dá)式被證明是一個(gè)挑戰(zhàn)。在第5章的稍后部分,您將學(xué)習(xí)如何創(chuàng)建多個(gè)過(guò)濾器上下文。那時(shí),代碼的復(fù)雜性增加了很多。但是,如果您了解評(píng)估上下文,那么這些方案很簡(jiǎn)單。在進(jìn)入DAX的下一個(gè)層次之前,您需要充分了解評(píng)估上下文。這就是為什么我們敦促您再次閱讀這節(jié)(甚至到目前為止的本章),直到這些概念明確為止。這將使閱讀下一章變得更加容易,您的學(xué)習(xí)體驗(yàn)也會(huì)更加順暢。
在離開(kāi)此示例之前,我們需要解決最后一個(gè)細(xì)節(jié)——即使用1、2、3的序列而不是到目前為止獲得的序列進(jìn)行排名。該解決方案比預(yù)期的要容易。實(shí)際上,在前面的代碼中,我們著重于對(duì)價(jià)格較高的產(chǎn)品計(jì)數(shù)。如是,該公式對(duì)14個(gè)排名第1的產(chǎn)品進(jìn)行了計(jì)數(shù),并將15分配給了第二排名級(jí)別。但是,對(duì)產(chǎn)品進(jìn)行計(jì)數(shù)不是很有用。如果公式計(jì)算的價(jià)格高于當(dāng)前價(jià)格,而不是產(chǎn)品價(jià)格,則所有14個(gè)產(chǎn)品都將折疊為一個(gè)價(jià)格。
'Product'[UnitPriceRankDense] =
VAR PriceOfCurrentProduct = 'Product'[Unit Price]
VAR HigherPrices =
FILTER (
VALUES ( 'Product'[Unit Price] ),
'Product'[Unit Price] > PriceOfCurrentProduct
)
RETURN
COUNTROWS ( HigherPrices ) + 1
圖4-13顯示了新的計(jì)算列以及UnitPriceRank。

最后的一小步是計(jì)算價(jià)格而不是產(chǎn)品計(jì)數(shù),這似乎比預(yù)期的要難。使用DAX的工作越多,就越容易開(kāi)始考慮為計(jì)算目的而創(chuàng)建的臨時(shí)表。
在此示例中,您了解了處理同一表上多個(gè)行上下文的最佳技術(shù)是使用變量。請(qǐng)記住,變量是DAX語(yǔ)言于2015年晚些時(shí)候引入的。您可能會(huì)發(fā)現(xiàn)現(xiàn)有的DAX代碼(在變量時(shí)代之前編寫)使用了另一種技術(shù)來(lái)訪問(wèn)外部行上下文: EARLIER 函數(shù),我們將在下一節(jié)介紹
使用 EARLIER 功能
DAX提供了訪問(wèn)外部行上下文的功能: EARLIER 。 EARLIER 通過(guò)使用上一個(gè)行上下文而不是最后一個(gè)行上下文來(lái)檢索列的值。因此,我們可以使用 EARLIER(Product [UnitPrice])來(lái)表示 PriceOfCurrentProduct 的值。
許多DAX新手對(duì) EARLIER 感到恐懼,因?yàn)樗麄儗?duì)行上下文的了解不夠充分,并且他們沒(méi)有意識(shí)到可以通過(guò)在同一張表上創(chuàng)建多個(gè)迭代來(lái)嵌套行上下文。理解行上下文和嵌套的概念后, EARLIER 是一個(gè)簡(jiǎn)單的函數(shù)。例如,以下代碼不使用變量即可解決以前的情況:
'Product'[UnitPriceRankDense] =
COUNTROWS (
FILTER (
VALUES ( 'Product'[Unit Price] ),
'Product'[UnitPrice] > EARLIER ( 'Product'[UnitPrice] )
)
) + 1
注意 EARLIER 接受第二個(gè)參數(shù),這是要跳過(guò)的步驟數(shù),以便可以跳過(guò)兩個(gè)或多個(gè)行上下文。此外,還有一個(gè)名為 EARLIEST 的函數(shù),該函數(shù)使開(kāi)發(fā)人員可以訪問(wèn)為表定義的最外面的行上下文。在現(xiàn)實(shí)世界中,既不經(jīng)常使用EARLIEST 也不使用 EARLIER 的第二個(gè)參數(shù)。盡管在計(jì)算列中通常有兩個(gè)嵌套行上下文,但很少有三個(gè)或更多嵌套行上下文。此外,自變量問(wèn)世以來(lái), EARLIER 實(shí)際上已變得毫無(wú)用處,因?yàn)樽兞坑梅ㄈ〈?EARLIER 。
學(xué)習(xí) EARLIER 的唯一原因是能夠讀取現(xiàn)有的DAX代碼。沒(méi)有其他理由在較新的DAX代碼中使用 EARLIER ,因?yàn)樵谠L問(wèn)行上下文時(shí),變量是保存所需值的更好方法。為此目的使用變量是一種最佳方法,并且可以使代碼更具可讀性。