DAX專題9:FILTER函數(shù)-讀書筆記(17)

FILTER()是DAX中最強大的表函數(shù)之一,在前兩章中我們介紹過:表函數(shù)的主要目的是可以讓我們在度量值中創(chuàng)建和使用虛擬表。這不是DAX中表函數(shù)的唯一用途,但這種用法是最常見的。這些虛擬表非常有用,因為我們可以使用FILTER表帶來的所有好處,而不需要在PBI模型中把它變成實體表。當FILTER()和CALCULATE函數(shù)雙劍合璧時,它允許你以任何你想要的方式改變公式中的篩選(也就是篩選上下文)。
語法:
= FILTER(Table, [Filter])
FILTER第一參數(shù)的表,可以是模型中的任何表,或者來自于表函數(shù)創(chuàng)建的虛擬表。
FILTER第二參數(shù),是任何返回結果為 True/False 的表達式。
開始在CALCULATE()中使用FILTER()之前,我們有必要看一下FILTER()如何工作的兩個概念。
?FILTER函數(shù)返回的表來自模型的數(shù)據(jù)源表,它可以包括0行,或更多行數(shù)據(jù)。FILTER函數(shù)的作用就是按第二參數(shù)的條件從第一參數(shù)表中篩選出符合條件的表。從技術上來講,F(xiàn)ILTER函數(shù)是一個迭代函數(shù),它逐行掃描第一參數(shù)表,然后逐行看本行數(shù)據(jù)是否符合篩選條件(FILTER第二參數(shù)),如果符合就留下這一行,不符合就扔掉,直到把第一參數(shù)的表所有行都過濾一遍。與其把FILTER函數(shù)看成是迭代器函數(shù),不如把它的工作方式當成是表中的計算列。
一、FILTER 函數(shù)有點像計算列 FILTER IS KIND OF LIKE A CALCULATED COLUMN
FILTER函數(shù)的行為跟計算列有點類似。為了正好的解釋這一點,我們通過一個計算列的演示來證明一下。
我們先轉到數(shù)據(jù)視圖,右鍵單擊Customers 表,選擇新建列,在公式欄輸入Test = Customers[YearlyIncome],可以看到新列的值是跟[YearlyIncome]的值是一一對應的拿過來的

然后我們把公式改一下,Test = Customers[YearlyIncome]>=80000,新建列的值根據(jù)判斷的結果,變成了TRUE或FALSE。

現(xiàn)在我們想一下TRUE或FALSE是怎么來的,Customers[YearlyIncome]每一行的值都與80000進行比較,把比較的結果添加到新建列的每一行中。我們在新建列中點擊篩選按鈕,選擇TRUE, Customers[YearlyIncome]>=80000的所有行就被篩選出來了,一共是4382位客戶年收至少是80000. 新建列的生成過程是對表中的每一行進行計算,然后把結果放在與新列對應的行單元格中。

二、要多用FILTER函數(shù),而不是使用計算列
實際上我們可以把FILTER的運算過程當成上節(jié)例子中的新建列,F(xiàn)ILTER運算開始時我們想像成:FILTER先把要篩選的表拷貝出一份來當成一個虛擬表(FILTER的第一參數(shù)表),然后用Test = Customers[YearlyIncome]>=8000在這個虛擬表中添加一個列返回TRUE/FALSE(FILTER的第二參數(shù)),新建列完成以后FILTER在新建列中點開篩選按鈕,選擇TRUE,篩選出新建列中結果為TRUE的表的所有行。這個過程就是FILTER背后的操作流程。
FILTER函數(shù)的最大優(yōu)點就是:它不需要在實體表中真的添加一個新列,而是在內存中完成新建列、篩選列的操作,一旦FILTER計算完成,虛擬的新列就完成任務在內存中釋放了。
現(xiàn)在我們知道了FILTER的運算過程,我來給大家演示一下現(xiàn)實在FILTER的等效操作。再說明一點:FILTER是表函數(shù),我們不能把它直接放到度量值里面,就像不能把一列放在度量值里面一樣。度量值必須返回一個值,不能返回一個表或表的列。為了便于理解這一點,下一節(jié)向大家介紹另一個思路,并給大家介紹新建表功能的使用。
三、新建表又來了(實際上我們是用可見表來 解釋Filter 返回的虛擬表)
大家回想一下上一節(jié)中第一段文字的描述,在腦海中腦補一下FILTER的工作過程。這次我們把剛才解釋FILTER在內存中運行的過程實實在在的展示出來。點擊新建表按鈕,在公式欄輸入:
Customers > 80000 Table =?
??FILTER(ALL(Customers[CustomerKey],Customers[Name],Customers[YearlyIncome]),
? ? Customers[YearlyIncome] >= 80000
? ? )

可以看到,F(xiàn)ILTER函數(shù)返回的表的行數(shù)跟上一節(jié)示例中演示的結果是一樣的。
大家要記?。何覀儎?chuàng)建這個表只是為了展示發(fā)生了什么,實際使用中我們不需要創(chuàng)建這個表,只要把它作為度量中的虛擬表(篩選參數(shù)表)使用就行了。
四、在Calculate里面使用Filter函數(shù)
這一節(jié)我們看看如何中度量值中使用FILTER函數(shù)。轉到報表視圖,新建一個矩陣,把Customers[Occupation]放到行上,把度量值 [Total Number of Customers]放到值上,矩陣展示出每個occupation type的客戶數(shù)量。

下面我們用FILTER寫一個度量值,使用上節(jié)的FILTER公式 = FILTER(Customers, Customers[YearlyIncome] >= 80000),剛才提到過:表不能放在度量值中,所以要給這個公式套上個聚合函數(shù),我們使用COUNTROWS,
Total Customers with Income of $80,000 or above Measure =
? ? ? COUNTROWS(FILTER(Customers, Customers[YearlyIncome] >= 80000))

沒有使用FILTER函數(shù)篩選度量值
五、Filter 不改變初始篩選上下文?
上一節(jié)的例子中,我們看到不是所有的職業(yè)中都有年收入大于80000的客戶,clerical 和 manual 返回的就是空值。
Total Customers with Income of $80,000 or above Measure =
? ? COUNTROWS(FILTER(Customers, Customers[YearlyIncome] >= 80000)? ? )
度量值中FILTER的第一參數(shù)是Customers表,在可視化報表中(如矩陣)這個表就不是數(shù)據(jù)視圖中的整個Customers表了,它依賴矩陣的初始上下文。我們之前講過矩陣中的每個值單元格是如何計算的,上一節(jié)的度量值放到矩陣中時,對于矩陣中的每個值單元格,傳遞給FILTER第一參數(shù)的表不是整個Customers,是每個值單元格對應的矩陣的行字段篩選后的表(這就是初始篩選)([Occupation]="Clerical"--下圖紅框篩選Customers),篩選以后再按FILTER第二參數(shù)篩選一次(Customers[YearlyIncome] >= 80000再篩選一次Customers),然后COUNTROWS再對兩次篩選后的表計算行數(shù)。 每個值單元格都是這樣計算的。這就解釋了為什么有的職業(yè)年收入大于80000的客戶為空值。

原文是這樣描述的:
The first row of the matrix above is evaluated as follows:
1. The matrix visual places a filter on Customers[Occupation] = "Clerical". As a result, the Customers table is now filtered to the 2,928 rows that are clerical customers.
2. The first parameter of FILTER() therefore receives a filtered copy of the Customers table, filtered for Customers[Occupation] = "Clerical".
3. Before FILTER() even starts to do its stuff, the Customers table has already been filtered by the visual. Instead of the first parameter (the table) having 18,484 rows, it actually has only 2,928 rows because it is already filtered for clerical customers.
六、要點
?? These are the key points to take away from the examples before:
? FILTER() returns a table. It is a virtual table, and hence you can't see it.
? You can think of FILTER() as operating like a calculated column inside a table—but better . It is better because it can do everything a calculate column can do (in this case) but doesn't need to materialise a new column into the table. In Power BI, fewer columns is always better ? You can materialise a FILTER() function by using the New Table option if you want to see how it works.
? The virtual table returned by FILTER() has a virtual relationship to its source; this is called lineage. The virtual table and virtual relationship behave in the same way that physical tables and relationships behave.
? The virtual table returned by FILTER() can be used inside a measure without the need to materialise the table at all.
? Materialising can be a great technique to help you visualise what is going on in DAX. You should materialise tables in order to learn and comprehend, but you should not leave such tables in your model. Delete them when you are done using them.
? You can't use table functions (naked tables) on their own inside measures; they must be used inside other functions so that they return single values. In the example above, for example, I used COUNTROWS() to count the rows in the table.
?FILTER()返回一個表,它是一個虛擬表,因此無法看到它。
?可以將FILTER()看作是在表中計算列的操作,它可以完成計算列所能做的所有事情而不需要將新列物化到實體表中。
?在Power BI中,表的列越少越好。
?如果你想看看它是如何工作的,你可以使用新建表來把FILTER()函數(shù)返回的表具象化。
?FILTER()返回的虛擬表和FILTER原表(第一參數(shù)表)實際上存在著一種虛擬關系,這就是所謂的數(shù)據(jù)沿襲。虛擬表和虛擬關系能做的與模型中實體表和模型的關系能做的是一樣的(像篩選、篩選傳遞等)。
?FILTER()返回的虛擬表可以用在度量中,不需要轉換成實體表。
?將虛擬表具象化是一種很好的方法,這樣做可以幫你把DAX中發(fā)生的事情復現(xiàn)化。在學習和理解DAX概念和計算過程時,應該將表實體化,實體化的表在測試完數(shù)據(jù)或度量值以后就刪除了吧,模型中其實不需要保留這樣的表。
?不能在度量值中單純放一個表函數(shù)(就是我們常說的裸表,度量值中放表的列也是不可以的),它們必須和其它函數(shù)配合使用,以返回單個值(標量值)。例如,在上面的示例中,我使用COUNTROWS()來計算表中的行數(shù)。
?? A Caution Related to Using FILTER()
Generally, it is fine to use FILTER() on lookup tables, but it’s somewhat riskier to use it on data tables, particularly if they are very large (with hundreds of millions of rows). Where to use FILTER() depends on your data, on the quality of your DAX formulas inside FILTER(), and on what you need to achieve. You will increase your depth of understanding with practice and experience. When you have a choice, aim to use FILTER() on the smallest table possible to keep your formulas working efficiently.
一般來說,在維度表上使用FILTER()是可以的,但在數(shù)據(jù)表上使用它有點風險,特別是如果數(shù)據(jù)表非常大(假如有數(shù)億行)。在哪里使用FILTER()取決于你的數(shù)據(jù)量、FILTER中DAX公式的代碼質量以及你的目的。通過不斷練習和經驗積累,你會增加對DAX理解,當你有選擇的余地時,盡量在盡可能小的表上使用FILTER(),好讓公式更高效地工作。
七、 CALCULATE里面使用FILTER? ?FILTER??Inside CALCULATE()
本章開頭說過,F(xiàn)ILTER函數(shù)通常用作CALCULATE函數(shù)中的表篩選參數(shù)。FILTER()是一個表函數(shù),它對表中的每一行進行操作來確定保留哪些行。因此FILTER()允許對表進行非常細粒度的計算,并且是一個非常強大的工具,可以按照我們想要的任何方式和詳細級別更改可視化對象的篩選上下文,而在CALCULATE()中使用簡單篩選(布爾表達式篩選)并不總是可以做到這一點。
Total Customers with Income of $80,000 or above 2 =
? ? CALCULATE(COUNTROWS(Customers),Customers[YearlyIncome] >= 80000)
這個公式返回的結果和度量值
Total Customers with Income of $80,000 or above Measure =
? ? COUNTROWS(FILTER(Customers, Customers[YearlyIncome] >= 80000))
返回的結果完全相同。第一個使用 CALCULATE寫的度量值公式中使用了簡單篩選(布爾表達式篩選),簡單篩選就是使用判斷表達式進行的篩選,CALCULATE函數(shù)設計之初是接受這種寫法的,實際上這是CALCULATE函數(shù)完全寫法的一種簡化(也可以稱之為語法糖),這樣寫只是為了書寫簡單度量值時更方便。這種寫法的背后,隱藏著它的完整語法書寫形式:
Total Customers with Income of $80,000 Under the Hood =
? ? CALCULATE(COUNTROWS(Customers),FILTER(ALL(Customers[YearlyIncome]),? ? ? ? ? ? Customers[YearlyIncome] >= 80000))
注意:FILTER()函數(shù)的第一個參數(shù)使用了ALL(Customers[YearlyIncome]),而不僅僅是Customers。下一章中會給大家舉幾個更詳細的例子來說明為什么在這里使用ALL函數(shù)。CALCULATE使用簡單條件篩選有一些限制,如果你想做一些復雜的事情的話,還是需要使用CALCULATE的完整語法形式。
八、示例:計算購買金額大于某值的客戶數(shù)量 Example: Calculating Lifetime Customer Purchases
現(xiàn)在我們要計算購買金額大于3000元的全部客戶數(shù)量,我們不能在CALCULATE函數(shù)中使用簡單篩選,因為表中沒有這樣一個列,含有一個單一值,這個值又包括每個客戶的所有購買金額。我們試著這樣寫一個
Customers with Sales Greater Than $3,000 Doesn't Work =
? ? ? ? ? ? ? ? ? ?CALCULATE(COUNTROWS(Customers), [Total Sales] > 3000)
公式里面第二參數(shù)是[Total Sales] > 3000,左邊是一個度量值,右面是一個數(shù)值,在DAX里面這樣寫是語法上不允許的,DAX無法運行這個公式。
報錯信息 :函數(shù)"PLACEHOLDER"已用于一個True/False布爾表達式,該表達式用作表篩選表達式。這是不允許的。
這種情況下,還是需要使用表篩選函數(shù)做為CALCULATE的第二參數(shù)
Customers with Sales Greater Than $5,000 = CALCULATE(COUNTROWS(Customers),
? ? ? ? ? ? ? ? ? ? ? ? ? ?FILTER(Sales,?[Total Sales] > 3000))

九、 對比一下Filter的簡寫語法和完全寫法 Simple Filter Syntax vs. Table Filter Syntax
如果你的篩選條件只是想比較一個列和一個值,你就可以使用CALCULATE的簡單篩選寫法。如果你想保持簡單的寫法,CALCULATE能夠很好的處理這種簡單篩選。如果想處理復雜的情況,不單單是比較一個列和一個值的話,那只用CALCULATE函數(shù)就處理不成了(這里指的是不用第二參數(shù))。這時CALCULATE需要它的強大幫手FILTER來幫忙。CALCULATE委托FILTER函數(shù)把符合條件的行篩選出來,F(xiàn)ILTER把篩選出來的表再回傳給CALCULATE,CALCULATE第一參數(shù)表達式計算出結果,這兩個函數(shù)相互配合完成工作。
Customers with Sales Greater Than $5,000 = CALCULATE(COUNTROWS(Customers),
? ? ? ? ? ? ? ? ? ? ? ? ? ?FILTER(Sales,? [Total Sales] > 3000))
本例中我們理解了FILTER的工作原理,它和稍早包含Customers[YearlyIncome] >= 80000的代碼還是稍有不同的。不論是簡單篩選還是表篩選,CALCULATE函數(shù)總是先運行篩選再計算。
我們再看看上面公式中FILTER(Sales, [Total Sales] > 3000)部分,第一小節(jié)中我們說過,F(xiàn)ILTER函數(shù)運行過程跟新建列的過程類似,我們把度量值[Total Sales]寫成新建的列的代碼,

新建列的值是每種產品的總銷量,從直觀上就能看出來每種產品的總銷量是相同的(事實表中有很多產品名稱重復的記錄,相同名稱的產品的總銷量是相同的,不同名稱的產品銷量是不同的,這也是度量值天生能被篩選的特質得到的)。如果你更深入地思考這個結果,你會發(fā)現(xiàn)實際上有很多事情發(fā)生。度量值[Total Sales] 公式代碼是 樣的? Total Sales = SUM(Sales[ExtendedAmount]),Total Sales把Sales表的產品銷量列做了匯總求和,但新建列是在Customers(客戶)表里的,不是在Sales表里,新建列公式卻干得很好,至于為什么?大家可以自行研究一下。
十、重新審視一下上下文轉換這個概念 Revisited Transition Context
上一節(jié)的示例中,我們把度量值放在新建列里,公式是 TEST NEW COLUMN = [Total Sales],度量值[Total Sales] = SUM(Sales[ExtendedAmount]),
得到了想要的結果。如果我們在新建列里輸入公式 TEST 3 SUM= SUM(Sales[ExtendedAmount]),得到的值全是一樣的,沒按產品區(qū)分,顯然這是不對的,為什么會這樣呢?

在概念6那一章時給大家講過一個"看不見的CALCULATE引擎? ?The Hidden Implicit CALCULATE()",? ? 其實我們把度量值放在新建列里時,隱藏在度量值公式里的CLACULATE就把行上下文轉換成了篩選上下文,正是這個上下文轉換,表的每一行變成了一個篩選器,這個篩選器順著模型關系從Customers表傳遞給Sales表,Sales表篩選后,度量值計算時每個客戶的銷量匯總就按每個客戶篩選區(qū)分了。但TEST 3 SUM= SUM(Sales[ExtendedAmount])只是行上下文,沒有轉換成篩選上下文,所以每一行的值都是數(shù)量的匯總。
下面我們把新建列里的度量值代碼修改一下:TEST NEW COLUMN = [Total Sales] >=3000, 新建列的值變成了TRUE/FALSE,我們再點擊新建列的篩選箭頭,選擇TRUE,結果如下:

我們這樣做只是為了幫助大家理解和學習,并不是每次想計算時我們都要創(chuàng)建一個新列,這種做法也不是解決問題的最好辦法,我們應該使用FILTER函數(shù)。FILTER函數(shù)工作時就可以在任何表中添加一個虛擬列,而不是在實體表中真的新建一個列。我們再把完整版公式寫一下
Customers with Sales Greater Than $5,000 = CALCULATE(COUNTROWS(Customers),
?FILTER(Sales,? ? [Total Sales] > 3000)?)
這個公式運行步驟如下:
1. FILTER()函數(shù)創(chuàng)建一個新的虛擬表,其中包含一個虛擬的True/False計算列。
2. FILTER()函數(shù)通過篩選新建列,只保留新列中值為True的行。
3.FILTER()返回篩選后的虛擬表,CALCULATE()用這個表篩選模型。
4. FILTER()創(chuàng)建的虛擬表格不可見,但可以通過使用剛剛的示例通過計算列或使用"新建表"按鈕實現(xiàn)公式的FILTER()部分來模擬它的工作。
在CALCULATE中使用FILTER函數(shù),雖然沒有永久存儲新表或新列來給數(shù)據(jù)模型添麻煩,但是可以很好的完成復雜的篩選。
十一、FILTER函數(shù)練習
1、求銷售金額(大于0,小于10000元)
total sales less 10000 and morethan 0 = CALCULATE([TOTAL SALES],
FILTER(Products,[total sales] <10000 && [total sales] > 0))
/*? ?= CALCULATE([Total Sales],?FILTER(Products, [Total Sales] <10000),? ? ? ? FILTER(Products, [Total Sales] >0))? ?*/
2、求銷售金額(大于0,小于10000元)的客戶數(shù)量
total sales productsnumber = CALCULATE(COUNTROWS(Products),
? ? FILTER('Products',[TOTAL SALES] <10000 && [total sales] >0))
/* = CALCULATE(COUNTROWS(Products),?FILTER(Products, [Total Sales] <10000),? ? ? ? FILTER(Products, [Total Sales] >0))*/

十二、進一步理解上下文轉換
在概念6那一章和本章的前面小節(jié)我們介紹了上下文轉換的概念,我們通過新建列演示了FILTER是如何進行上下文轉換的。上下文轉換是個比較難理解的概念,尤其比較難的腦補的地方是在像FILTER返回的虛擬表中轉換上下文時,因為FILTER返回的表中發(fā)生的上下文轉換是不可見的。之前我們寫的度量值是這樣的:
Customers with sales greater than $5,000 =?
CALCULATE(COUNTROWS(Customers),?FILTER(Customers, [Total Sales] >= 5000))
公式里面的 [Total Sales] 公式: [Total Sales] =SUM(Sales[ExtendedAmount]),如果我們把度量值里面的[Total Sales]換成SUM(Sales[ExtendedAmount]),上面度量值公式變成這樣
Customers with sales greater than $5,000 Version2 =?? ?CALCULATE(COUNTROWS(Customers),?FILTER(Customers,SUM(Sales[ExtendedAmount]) >= 5000))
total customers that have purchased = CALCULATE([TotalCustmersNumber],
FILTER(Customers,[TOTAL SALES]>0))
[Total Sales] = SUM(Sales[ExtendedAmount])
我們把這幾個度量值放到矩陣里面

Ver2度量值返回的是一個不能篩選的值,因為FILTER的第二參數(shù) SUM(Sales[ExtendedAmount]是一個行上下文,不能被篩選。
我們寫度量值時[Total Sales] = SUM(Sales[ExtendedAmount]),PBI在后臺其實給公式套上了一個CALCULATE函數(shù),
[Total Sales] = CALCULATE(SUM(Sales[ExtendedAmount]))
所以,如果我們把Ver2改一下:
Customers with sales greater than $5,000 Version3 =? ? ?CALCULATE(COUNTROWS(Customers),?FILTER(Customers,CALCULATE(SUM(Sales[ExtendedAmount]) >= 5000)))

下面我們把矩陣的行字段換成國家,可以看到度量值不會管誰在矩陣行上篩選,它都能正常工作

再給大家啰嗦一下:SUM(Sales[ExtendedAmount])外面沒有套上CALCULATE時,上下文轉換是不會自己完成的。SUM(Sales[ExtendedAmount])外面有CALCULATE時,或者隱式的存在一個CALCULATE時,上下文轉換都能完成,就能得到正確的結果。所以有些情況下需要我們手動添加一個CALCULATE函數(shù)才能讓公式正常運行。在第11章我們也說過,行上下文不能自動轉換成篩選上下文。之前在新建列時我們談到的上下文轉換的情況跟FILTER上下文轉換的情況是一樣的。剛剛的示例VER2度量值中FILTER函數(shù)沒有顯式的CALCULATE,也沒有隱式的CALCULATE,所以Customers表不能把篩選傳遞給Sales表。VER3公式中我們給FILTER里面加了一個CALCULATE后,Customers表就可以傳遞篩選了,結果就對了。
十三、重提虛擬表的數(shù)據(jù)沿襲這個概念 Virtual Table Lineage Revisited
在第13章我們介紹過虛擬表的數(shù)據(jù)沿襲問題,把虛擬表想像成一個實體表放在模型中,重新看這個概念也是為了鞏固一下知識點。
Customers with Sales Greater Than $5,000 =? ? CALCULATE(COUNTROWS(Customers),
FILTER(Customers, [Total Sales] >= 5000))
我們可以把上面這個公式中FILTER函數(shù)返回一個虛擬表想像成是模型中的一個表。表函數(shù)創(chuàng)建的虛擬表和模型中的其它表也具有關系,我們把這種關系的延伸叫“數(shù)據(jù)沿襲”。我更喜歡用視覺化的方式把表函數(shù)創(chuàng)建的虛擬表在模型中的樣子放在模型視圖中。

FILTER函數(shù)篩選出來的表是一個數(shù)據(jù)子集(產生這個虛擬表的原表的子表),虛擬表和產生虛擬表的原表保持著關系的延續(xù)(數(shù)據(jù)沿襲)。當這個表在CALCULATE函數(shù)中被使用時,CALCULATE會把FILTER篩選出的虛擬表做為篩選條件順著關系箭頭方向傳遞給模型的其它表。上面這個圖是想像出來的,只是為了具象化虛擬表和模型的關系,虛擬表的所有行為遵循上圖中關系的約束。
NOTE:通過新建表菜單創(chuàng)建的表(實體表)跟模型并沒建立關系,如果想讓這種表篩選模型,那就需要手工建立 和其它表的關系。