在本章中,您將學習DAX中標量函數和表函數之間的區(qū)別。表函數對于DAX中的內部計算非常重要,當您為度量值或計算列編寫DAX查詢而不是DAX表達式時,表函數非常有用。
本章的目標是介紹表函數的概念,而不是對您第一次在這里看到的所有函數提供詳細的解釋。第9章“高級表函數”對表函數進行更深入分析。這里,我們將解釋表函數在DAX中的作用,以及如何在常見場景中使用它們,包括在標量DAX表達式中。
3.1 介紹表函數
DAX是一種函數式語言,您可以在其中編寫表達式,在計算后生成結果。到目前為止,您已經看到DAX表達式通常返回單個值,例如字符串或數字。我們將這些表達式稱為標量表達式。定義度量值或計算列時,你總是編寫標量表達式,如下所示:
= 4 + 3
= "DAX is a beautiful language"
= SUM ( Sales[Quantity] )
但是,您可以編寫一個DAX表達式來生成一個表。您不能將表表達式直接分配給度量值或計算列,但表表達式是DAX的重要組成部分。例如,有DAX函數接收表表達式作為參數,并且需要表表達式來編寫DAX查詢。
表表達式的最簡單示例是在DAX表達式中引用表名,例如以下表達式返回Sales表的整個內容(所有列和所有行):
= Sales
但是,如果您試圖將上一個表達式分配給度量值或計算列,則會出現錯誤,因為度量值需要標量值作為結果。您需要操作表表達式以獲取標量值。這可以通過使用接受表表達式作為參數的函數來實現。例如,使用COUNTROWS計算表中包含的行數:
= COUNTROWS ( Sales )
COUNTROWS函數具有以下定義:
COUNTROWS ( <table> )
當一個DAX函數接受表表達式作為參數時,您可以在該參數中寫入表的名稱,或者您可以編寫一個返回表的函數。
我們根據DAX函數的返回類型對其進行分類。我們稱返回標量值的函數為“標量函數”,稱返回表的函數為“表函數”。例如,COUNTROWS是一個標量函數,因為它接受一個表作為參數,并返回一個數字。
許多表函數通常操作表,更改原始表的行或列。例如,您可以使用以下表達式,計算Sales表中價格大于100的行數:
= COUNTROWS (
FILTER (
Sales,
Sales[Unit Price] > 100
))
在上一個表達式中,FILTER返回一個僅包含Sales中單位價格大于100的行的表。在本章后面,您將了解有關FILTER函數的更多信息。
通常,您在代碼中使用表表達式來迭代表的行并聚合一些值,以返回標量值作為結果。您不能將表表達式直接分配給度量值和計算列。但是,您可以在計算表中使用表表達式(如果此功能將來可用)或在DAX查詢中使用表表達式,從而得到表達式的內容。
例如,您可以通過執(zhí)行以下表表達式來獲取Sales中單位價格大于100的行的表,您可以在圖3-1中看到返回的內容。
= FILTER (
Sales,
Sales[Unit Price] > 100
)

DAX還為您提供了EVALUATE語句,您可以使用它來計算表表達式:
EVALUATE
FILTER (
Sales,
Sales[Unit Price] > 100
)
您可以在可以執(zhí)行DAX查詢的任何客戶端工具(Microsoft Excel,DAX Studio,SQL Server Management Studio,Reporting Services等)中執(zhí)行上面的DAX查詢。在下一節(jié)中,您將看到EVALUATE語法的詳細說明。
3.2 EVALUATE 語法
您可以將DAX用作編程語言和查詢語言。
DAX查詢是一個返回表的DAX表達式,與EVALUATE語句一起使用。完整的DAX查詢語法如下:
[DEFINE { MEASURE <tableName>[<name>] = <expression> }]
EVALUATE <table>
[ORDER BY {<expression> [{ASC | DESC}]} [, ...]
[START AT {<value>|<parameter>} [, ...]] ]
初始DEFINE MEASURE部分可以用于定義查詢的本地度量(即,它們存在于查詢周期中)。當您調試公式時,它變得非常有用,因為您可以定義一個本地度量,對它進行測試,然后在它的行為符合預期時將其放入模型中。您將在第9章中看到此語法的更多示例。
大多數語法都是由可選參數組成的。最簡單的查詢是從現有表中檢索所有列和行:
EVALUATE Product
您可以在圖3-2中看到結果。

要控制排序順序,可以使用ORDER BY子句:
EVALUATE Product
ORDER BY
Product[Color],
Product[Brand] ASC,
Product[Class] DESC
備注:
請注意,模型中定義的“按列排序”屬性在DAX查詢中不起作用。即使您可能通過根據Sort By Column屬性查詢單個列而看到排序數據,您也不必依賴此行為,就像您不能依賴SQL查詢中的聚簇索引一樣。生成動態(tài)DAX查詢的客戶端應讀取模型元數據中的“按列排序”屬性,然后生成相應的ORDER BY條件。在DAX和SQL中,必須始終使用顯式ORDER BY條件來獲取排序數據。
ASC和DESC關鍵字是可選的;如果不填,則默認使用ASC。您可以在圖3-3中看到上一個查詢的結果,其中數據按顏色、品牌和類排序。

START AT條件也是可選的,并且只能與ORDER BY子句一起使用。您可以在ORDER BY語句中為每列指定起始值。 START AT條件對于無狀態(tài)應用程序中的分頁非常有用,該應用程序僅從查詢中獲取有限數量的行,然后在用戶請求下一頁數據時發(fā)送另一個查詢。例如,查看以下查詢:
EVALUATE Product
ORDER BY
Product[Color],
Product[Brand] ASC,
Product[Class] DESC
START AT
"Yellow", "Tailspin Toys"
查詢返回圖3-4中所示的表,其中僅包含從Yellow,Tailspin Toys開始的行。

請注意,“starting from”的概念取決于ORDER BY子句中指定的順序方向。如果您為Product[Brand]指定DESC,如以下示例所示,結果中不包括Wide World Importers,其他品牌(如Southridge Video和 Northwind Traders)跟著Tailspin Toys。您可以在圖3-5中看到以下查詢的結果。
EVALUATE Product
ORDER BY
Product[Color],
Product[Brand] DESC,
Product[Class] DESC
START AT
"Yellow", "Tailspin Toys"

要篩選DAX查詢返回的行并更改列,必須使用特定的表函數在EVALUATE關鍵字之后操作表表達式。本章介紹了一些表表達式,而第9章介紹了其他表表達式。
3.3 使用表表達式
正如您在本章開頭所看到的,您經常使用表表達式作為其他DAX函數的參數。典型的用法是在迭代表的函數中,為每一行計算DAX表達式。比如所有以“X”結尾的聚合函數,例如SUMX:
[Sales Amount] :=
SUMX (
Sales,
Sales[Quantity] * Sales[Unit Price]
)
您可以使用表函數替換簡單的Sales表引用。例如,您可以使用FILTER函數僅考慮數量大于1的Sales:
[Sales Amount Multiple Items] :=
SUMX (
FILTER (
Sales,
Sales[Quantity] > 1
),
Sales[Quantity] * Sales[Unit Price]
)
在計算列中,還可以使用RELATEDTABLE函數檢索一對多關系的多端表的所有行。例如,Product表中的以下計算列計算相應產品的銷售額:
Product[Product Sales Amount] =
SUMX (
RELATEDTABLE ( Sales ),
Sales[Quantity] * Sales[Unit Price]
)
您可以在第4章“理解計算上下文”中的“行上下文和關系”這一節(jié)中找到RELATEDTABLE表函數的詳細說明
您可以在同一DAX表達式中嵌套表函數調用,因為任何表表達式都可以是表函數的調用。例如,Product表中的以下計算列僅考慮數量大于1的銷售額來計算產品銷售額。
Product[Product Sales Amount Multiple Items] =
SUMX (
FILTER (
RELATEDTABLE ( Sales ),
Sales[Quantity] > 1
),
Sales[Quantity] * Sales[Unit Price]
)
當您對表函數進行嵌套調用時,DAX首先計算最內層函數,然后計算其他函數直到最外層函數。不要將此規(guī)則與函數調用參數進行求值的順序混淆。
備注:
稍后您將看到,嵌套調用的執(zhí)行順序可能會造成混淆,因為CALCULATETABLE的計算順序與FILTER不同。在下一節(jié)中,您將學習FILTER的行為,您將在第5章“理解CALCULATE和CALCULATETABLE”中找到CALCULATETABLE的介紹。
2.4 理解FILTER
FILTER函數有一個簡單的角色:它獲取一個表并返回一個表,這個表的列與原始表中的列相同,但是只包含滿足逐行應用的篩選條件的行。
FILTER的語法如下:
···
FILTER ( <table>, <condition> )
···
FILTER迭代<table>,并且對于每一行,計算<condition>,這是一個布爾表達式。當<condition>的計算結果為TRUE時,FILTER返回該行;否則,它會跳過它。
備注:
從邏輯上看,FILTER為<table>中的每一行執(zhí)行<condition>。但是,DAX中的內部優(yōu)化可能會將這些計算的數量減少到<condition>表達式中包含的列引用的唯一值的數量。<condition>的實際計算次數對應于FILTER操作的“粒度”。這種粒度決定了FILTER的性能,它是DAX優(yōu)化的重要元素。
例如,以下查詢篩選了Fabrikam品牌的產品,如圖3-6所示。
EVALUATE
FILTER (
Product,
Product[Brand] = "Fabrikam"
)

您可以在另一個FILTER函數中嵌套FILTER調用,因為您可以使用任何表表達式作為篩選參數。執(zhí)行的第一個FILTER是最內層的。通常來說,嵌套兩個篩選和在AND函數中使用一組邏輯條件返回的結果相同。換句話說,以下查詢產生相同的結果:
FILTER ( <table>, AND ( <condition1>, < condition2> ) )
FILTER ( FILTER ( <table>, < condition1> ), < condition2> ) )
但是,如果<table>有許多行且兩個判斷條件具有不同的復雜性,您可能會觀察到不同的性能。例如,請考慮以下查詢,該查詢返回Unit Price超過Unit Cost三倍的Fabrikam產品,如圖3-7所示。
EVALUATE
FILTER (
Product,
AND (
Product[Brand] = "Fabrikam",
Product[Unit Price] > Product[Unit Cost] * 3
))

此類查詢可能會將這兩個條件應用于Product表的所有行。如果兩者中有一個條件更快或更具選擇性,則可以使用嵌套的FILTER函數首先應用它。例如,以下查詢將Unit Price和Unit Cost的篩選應用于最里面的FILTER函數,然后按品牌篩選那些滿足價格條件的產品。
EVALUATE
FILTER (
FILTER (
Product,
Product[Unit Price] > Product[Unit Cost] * 3
),
Product[Brand] = "Fabrikam"
)
如果反轉條件,則還會反轉它們的執(zhí)行順序。以下查詢將價格條件應用于屬于Fabrikam品牌的產品:
EVALUATE
FILTER (
FILTER (
Product,
Product[Brand] = "Fabrikam"
),
Product[Unit Price] > Product[Unit Cost] * 3
)
當您優(yōu)化DAX表達式時,此知識將非常有用。您可以選擇執(zhí)行順序以有限應用最具選擇性的篩選器。但是,如果沒有清楚地理解計算上下文,請不要開始優(yōu)化DAX。您將在第16章“優(yōu)化DAX”中找到有關查詢優(yōu)化的更完整的討論。這些示例的目的是讓您了解表函數的嵌套調用的執(zhí)行順序。
備注:
通常,嵌套調用函數的執(zhí)行順序是從最內層到最外層的函數。您將看到CALCULATE和CALCULATETABLE可能是此行為的一個例外,因為用于計算其參數的特定順序。因為您可能在類似情況下使用FILTER和CALCULATETABLE,所以請注意嵌套調用的這種差異。
2.5 理解ALL、ALLEXCEPT和ALLNOBLANKROW
ALL是一個有用的函數,它返回表的所有行或列的所有值,具體取決于您使用的參數。例如,以下DAX查詢返回Product表中的所有行:
EVALUATE
ALL ( Product )
您不能在ALL參數中指定表表達式。您必須指定表名或列名列表。如果使用單個列,則結果是一個只有一列包含其唯一值列表的表,如圖3-8所示。
EVALUATE
ALL ( Product[Class] )

您可以在ALL函數的參數中指定同一個表中的更多列。如果使用多列,則結果將是具有等效列數的表,其中包含這些列中現有值組合的列表。例如,以下表達式生成如圖3-9所示的結果。
EVALUATE
ALL ( Product[Class], Product[Color] )
ORDER BY Product[Color]

在所有變體中,ALL忽略任何現有的篩選器以產生其結果。您可以使用ALL作為迭代函數的參數,例如SUMX和 FILTER,或者作為CALCULATE函數中的篩選器參數(稍后將會看到)。
如果要在ALL函數調用中包含表的大多數列,則可以使用ALLEXCEPT。ALLEXCEPT的語法需要一個表,后跟要從結果中排除的列。因此,ALLEXCEPT返回一個表,其中包含表的其他列中現有值組合的唯一列表。
實際上,ALLEXCEPT是一種編寫DAX表達式的方法,該表達式將自動在ALL結果中包含在將來版本中可能出現在表中的任何其他列。例如,如果您有一個包含五列(ProductKey、Product Name、Brand、Class、Color)的Product表,則以下語法會產生相同的結果:
ALL ( Product[Product Name], Product[Brand], Product[Class] )
ALLEXCEPT ( Product, Product[ProductKey], Product[Color] )
但是,如果您稍后添加兩列Product [Unit Cost]和Product [Unit Price],那么ALL的結果將忽略它們,而之前的ALLEXCEPT將返回相當于:
ALL (
Product[Product Name],
Product[Brand],
Product[Class],
Product[Unit Cost],
Product[Unit Price]
)
以下查詢返回一個表,該表包含Product表中除Product Code和Color之外的所有列。圖3-10中的結果與原始表的行數相同,因為結果包含ProductKey列,每列具有唯一值。結果中列的其他組合可能返回較少的行數,因為ALLEXCEPT會刪除返回列中重復的值組合。
EVALUATE ALLEXCEPT ( Product, Product[ProductKey],
Product[Color] )

在前面的示例中,您在EVALUATE語句中看到了ALL,該語句在沒有任何現有篩選器的情況下執(zhí)行DAX表達式。因此,最好查看一個示例,該示例使用度量值來計算數據透視表中ALL返回的行數,其中每個單元格使用不同的篩選器計算度量值。考慮以下度量值:
[Products] := COUNTROWS ( Product )
[All Products] := COUNTROWS ( ALL ( Product ) )
[All Brands] := COUNTROWS ( ALL ( Product[Brand] ) )
您可以在圖3-11中看到每個度量值的不同結果的示例。

對于每個產品類別,在“ALL Products”和“ALL Colors”列中始終具有相同的編號。 ALL語句的計算將忽略由數據透視表的每個單元格定義的篩選器。
在關系的父表上調用ALL時,如果子表包含一個或多個與父表中的任何值不匹配的行,則會檢索另一個空行。您可以使用ALLNOBLANKROW而不是ALL來從結果中省略此特殊行。
考慮以下度量值:
[All Products] := COUNTROWS ( ALL ( Product ) )
[All NoBlank Products] := COUNTROWS ( ALLNOBLANKROW ( Product
) )
[All Brands] := COUNTROWS ( ALL ( Product[Brand] ) )
[All NoBlank Brands] := COUNTROWS ( ALLNOBLANKROW (
Product[Brand] ) )
[All Sizes] := COUNTROWS ( ALL ( Product[Size] ) )
[All NoBlank Sizes] := COUNTROWS ( ALLNOBLANKROW (
Product[Size] ) )
在圖3-12中,您可以看到度量值ALL和ALLNOBLANKROW之間的區(qū)別。所有版本的度量值ALL都比ALLNOBLANKROW多一行。原因是Sales表中的部分行在Product表中沒有匹配的行,因此在Product表中虛擬添加了一行,您可以在圖3-12中的(空白)行中看到該結果。

您應該注意到,All Sizes和All NoBlank Sizes度量值始終返回相同的值。此度量值查詢Products [Size]列中的值的計數。在這種情況下,ALL和ALLNOBLANKROW函數返回相同的值,因為Products [Size]列已包含空值。在圖3-13中的示例中,有569個產品的Products [Size]是空白,另外還有一類空白產品,包含對Sales表中不匹配產品的引用,總共570個。所有這些行都歸為Products [Size]中的空白值。

當你編寫一個DAX公式,該公式忽略關系中的不匹配值,你應該使用ALLNOBLANKROW。然而,ALL的使用很常見,而ALLNOBLANKROW很少使用。
2.6 理解VALUES和DISTINCT
在上一節(jié)中,您已經看到ALL與一列一起使用會返回一個包含其所有唯一值的表。 DAX提供了另外兩個類似的函數,它們返回列的唯一值列表:VALUES和DISTINCT。
如果在沒有任何其他篩選器操作的EVALUATE語句中使用,則VALUES和DISTINCT看起來與ALL完全相同。但是,當您將這些函數放在DAX度量值中時,您可以觀察到不同的行為,因為計算發(fā)生在數據透視表的每個單元格的不同上下文中。
請考慮以下度量值,這些度量值計算“Product”表的“Brand”和“Size”列中的唯一值的數量。
[Products] := COUNTROWS ( Product )
[Values Brands] := COUNTROWS ( VALUES ( Product[Brand] ) )
[Distinct Brands] := COUNTROWS ( DISTINCT ( Product[Brand] ) )
[Values Sizes] := COUNTROWS ( VALUES ( Product[Size] ) )
[Distinct Sizes] := COUNTROWS ( DISTINCT ( Product[Size] ) )
VALUES返回當前單元格中可見的唯一值列表,包括不匹配值的可選空行。 DISTINCT執(zhí)行相同操作,而不包括不匹配值的可選空行。但是,如果空白值顯示為列的有效值,則2個函數都將包含空行。唯一的區(qū)別是為了處理關系中的缺失值而添加的可選空行。
一個例子可能有助于理解這種差異。如圖3-14所示,每個產品類別都會篩選不同數量的產品。例如,Deluxe類別有360種產品,有11個獨特品牌和204種獨特尺寸。 VALUES和DISTINCT返回相同的數字,但有一個例外:數據透視表行上的(空白)產品類別。結果包括為顯示不匹配產品的銷售額金額而添加的虛擬行。

另一個區(qū)別在于圖3-14的總計。應用于Product [Brand]的VALUES返回的值比應用于同一列的DISTINCT多一個值。但是,應用于Products [Size]的VALUES不會發(fā)生這種情況,它返回與相應列上的DISTINCT相同的值。原因是至少一個產品的“Distinct Sizes”列包含空值,因此添加的空白產品不會向“Distinct Sizes”列添加新的唯一值。
當沒有篩選器時,DISTINCT的行為對應于ALLNOBLANKROW,而VALUES的行為對應于ALL。
VALUES也接受一個表作為參數。在這種情況下,它返回當前單元格中可見的整個表,可選地包括不匹配關系的空行。例如,在數據模型中考慮以下度量值,其中Sales表與Product具有關系,并且包含具有與任何現有產品不匹配的產品密鑰的交易。
[Products] := COUNTROWS ( Product )
[Values Products] := COUNTROWS ( VALUES ( Product ) )
[All NoBlank Products] := COUNTROWS ( ALLNOBLANKROW ( Product
) )
[All Products] := COUNTROWS ( ALL ( Product ) )
您可以在圖3-15中看到,在這種情況下,當沒有篩選器時,VALUES的結果對應于ALL的行為,包括添加的空行以顯示不匹配產品的銷售額。在這種情況下,您不能在表上使用DISTINCT;如果存在重復的行,則沒有一個DAX函數可以刪除重復的行(您必須使用SUMMARIZE,稍后將在第9章中看到)。但是,度量值[Products]計算表中的行數,忽略可能的空行,當沒有篩選器時,對應ALLNOBLANKROW。

2.6.3 使用VALUES作為標量值
即使VALUES是表函數,您也經常使用它來計算標量值,因為您將在本節(jié)中學習DAX中的特殊功能。例如,您可以在以下表達式中找到VALUES,如果某個選擇的所有產品具有相同的顏色,則會顯示顏色名稱:
[Color Name] :=
IF (
COUNTROWS ( VALUES ( Product[Color] ) ) = 1,
VALUES ( Product[Color] )
)
您可以在圖3-16中看到結果。當“[Color Name]”列包含空白時,表示存在兩種或更多種不同的顏色。

這里有趣的一點是,我們使用VALUES的結果作為標量值,即使它返回一個表。這不是VALUES的特殊行為,但它是DAX語言的更一般行為:如果表表達式返回具有一行和一列的表,則可以轉換為標量值,并在需要時自動完成。
實際上,如果結果只有一行和一列,則可以將任何表表達式用作標量值。當表返回更多行時,您會在執(zhí)行時收到此錯誤:“提供了一個包含多個值的表,其中需要單個值?!币虼?,您應始終使用返回的條件保護轉換為標量值如果表表達式返回更多行,則表示不同的結果(在編寫DAX表達式時,您應該已經知道表表達式是否只返回一行)。
上一個示例的Color Name度量值使用COUNTROWS來檢查Products表的Color列是否只選擇了一個值。執(zhí)行完全相同控件的一種更簡單的方法是使用HASONEVALUE,它執(zhí)行相同的檢查,如果列只有VALUES返回的值,則返回TRUE,否則返回FALSE。以下兩種語法是等效的:
COUNTROWS ( VALUES ( <column> ) ) = 1
HASONEVALUE ( <column> )
您應該使用HASONEVALUE而不是COUNTROWS有兩個原因:它更具可讀性,而且可能稍快一些。以下是基于HASONEVALUE的[Color Name]度量值的更好實現:
[Color Name] :=
IF (
HASONEVALUE ( Product[Color] ),
VALUES ( Product[Color] )
)
您經常使用VALUES作為標量表達式的原因是它返回單個列,并且可能返回單個行,具體取決于計算上下文。使用VALUES作為標量表達式在許多DAX模式中很常見,并且在本書中反復出現。