PowerBI之DAX


計算表、計算字段和度量

計算表

返回值是一個二維表,比如下面返回一個只有一個時間列的表。時間是連續的,結束於6月。會掃描模型里的最大時間和最小時間,然后涵蓋掉。

Due Date = CALENDARAUTO(6)

計算列

單行內計算,非聚合。和我們的非聚合型計算字段類似。

Due Fiscal Year =
"FY"
	& YEAR('Due Date'[Due Date])
		+ IF(
			MONTH('Due Date'[Due Date]) > 6,
			1
		)
Due Month =
FORMAT('Due Date'[Due Date], "yyyy mmm")

注意,單行內的計算,是不能跨表的,這點和我們是一樣的。但是也允許通過RELATEDRELATEDTABLELOOKUPVALUE函數引用其他關聯表的字段,比如下面這樣,跨sales表和product兩張表的字段:

Discount Amount =
(
	Sales[Order Quantity]
		* RELATED('Product'[List Price])
) - Sales[Sales Amount]

度量

聚合后的字段稱為度量。度量分為隱式度量和顯式度量,用DAX創建的叫顯示度量,在報表里展示的數值,叫隱式度量。顯式度量是聚合的數值,類似於我們的聚合型計算字段、高級計算。尤其要提到的是,dax中,所有的高級計算,都是一個單獨的度量,在建模時就創建好的。

在不麻煩的情況下,PowerBI推薦建模者在建模的時候就創建好顯式度量,並且把隱式度量隱藏掉,防止做報表的人做出錯誤的聚合度量。

Revenue = SUM(Sales[Sales Amount])

Context

Power BI最最最重要的概念,可以說,Power BI的一切計算,都是圍繞Context進行的。

記住,在DAX中,是沒有group by的概念的。pbi在計算一個單元格或者表達時的值時,總是根據這個單元格或表達時所處的上下文來執行計算的。

Row Context

行上下文的意思就是當前行,一般在非聚合型計算字段上使用這個概念,只在行內計算。或者在迭代函數上使用這個概念,代表內外循環的兩個“當前行”。

需要注意的是,行上下文默認是不跨關聯表的。比如你有Product 和Sales兩張表通過order_id關聯在一起,你在Product表上新建一個totalCost=sum(Sales[shipping_cost])計算字段,那么所有行上的totalCost都是同一個值:shipping_cost總計值。它不會根據Product表上的維度做分組聚合。如果需要的話,必須通過RELATED 之類的函數引用關聯表字段,或者使用CALCULATE函數,讓Row Context轉為Filter Context(下面會講)。

Query Context

查詢上下文就是在報表查詢時確定一個單元格的上下文,它由單元格上的行頭列頭、報表上的各類過濾器、切片器共同決定,然后單元格上的表達式(可能是一個最簡單的聚合)就是在這些上下文中執行的。

Filter Context

這是所有context中最靈活最強大的。可以先把它看成是對數據的過濾條件,比如制作報表的時候,filter context就是添加的各種過濾器比如切片器、查詢控件,更為強大的是,他可以在創建度量的時候,直接寫到表達式中,影響表達式計算的范圍。更重要的是,你可以通過各種函數去改變動態修改filter context。由於filter context的優先級比其他context都要高,所以它可以改變聚合、計算的結果。

context轉移

指的是將Row Context 和Query Context轉為Filter Context,可以直白的理解為圖表上的單元格所在行的維度值(row context)變成了(轉移成)該單元格上表達式執行時的過濾條件(filter context)。發生了轉移之后,就能夠通過修改函數去修改表達式的context了,這也是dax能夠實現各種高級計算的秘訣。

寫好dax的關鍵是掌握context

寫好dax,尤其是復雜的dax,關鍵是在理解各種dax概念(context)的基礎上,用好各種聚合函數和filter context修改函數。

1.理解filter context 的概念,以及它的作用機制

2.掌握何時以及用什么方法去改變filter context以獲得正確的表達式結果

3.將各種表達式組合成更復雜的dax語句。

迭代函數

所有的聚合函數基本上都有一個迭代函數版本,函數名是聚合函名加上X后綴,比如 SUMX, COUNTX, MINX, MAXX 等等迭代函數是迭代一個表的所有行,他的聲明如下:

SUMX( table , expression)

第一個入參是一個表或者能夠返回一個表的表達式,第二個入參是一個表達式。迭代函數的做法是迭代table的每一行,並且在每一行的row context下執行expression,然后對每一行結果做入參執行聚合計算,得到一個出參。如果第一個入參是返回表的表達式,這個表達式是在當前filter context中執行的。

實際上,普通的聚合函數其實是迭代聚合的語法糖,在pbi中,簡單聚合都會變成迭代聚合函數,比如sum聚合

Revenue = SUM(Sales[Sales Amount])

其實是下面這個SUMX迭代函數的語法糖,它迭代了Sales表。

Revenue =
SUMX(
	Sales,
	Sales[Sales Amount]
)

迭代函數的作用

創建復雜聚合的計算字段

Discount =
SUMX(
	Sales,
	Sales[Order Quantity]
	* (
		RELATED('Product'[List Price]) - Sales[Unit Price]
	)
)

這個表達式迭代了Sales表,並且以Sales表每一行的row context去計算單行的折扣量,最后匯總成總的折扣。需要注意的是,這里用了一個RELATED函數,這是因為pbi的row context默認是不能跨關聯表的,如果不加這個函數,Product'[List Price]的值是不會受到Sales表當前行的維值約束的。

實現高階聚合

比如想實現每類商品的平均利潤,我們可能這樣寫

Revenue Avg =
AVERAGEX(
	Sales,
	Sales[Order Quantity] * Sales[Unit Price] * (1 - Sales[Unit Price Discount Pct])
)

但這其實不對,他計算的是每一行的的平均利潤,但是同一類商品可能會有很多很多行。我們真正想實現的,其實是先計算每一類商品的利潤,然后相加起來,再除以商品種類。

Revenue Avg Order =
AVERAGEX(
	VALUES('Product'[Product Type]),
	[Revenue]
)

這里的VALUES('Product'[Product Type])返回了所有的產品類型作為一個表,這個表只有一列,那就是產品類型,然后AVERAGEX函數迭代了這個表,在每一行(每種產品類型)以改行的row context去計算Revenue,最后算平均值。這里我們引用了Revenue度量,如果我們不是用引用,直接寫成下面這樣:

Revenue Avg Order =
AVERAGEX(
	VALUES('Product'[Product Type]),
	SUM(Sales[Sales Amount])
)

結果也是不一樣的,因為row context是不會跨關聯表的,所以產品類型不會影響SUM(Sales[Sales Amount]),那么每一行的SUM(Sales[Sales Amount])結果都是一樣的。如果要讓產品類型作用與SUM(Sales[Sales Amount]),則需要讓row context轉移成SUM(Sales[Sales Amount])表達式的filter context

Revenue Avg Order =
AVERAGEX(
	VALUES('Product'[Product Type]),
	CALCULATE(SUM(Sales[Sales Amount]))
)

calculate函數

calculate函數的用於修改filter context,實現強大的dax計算,他的基本格式如下

CALCULATE(<expression>, [[<filter1>], <filter2>]…)

表達式部分必須返回一個具體的值(數值、文本、時間等)。

篩選器部分,每個篩選器必須返回bool值或者一個數據表,各個filter之間是and關系。

bool篩選器

bool篩選器比較簡單,但是它有以下限制

  • 每個filter只能引用一個列
  • 不能引用度量
  • 不能用聚合函數

舉幾個引用bool篩選器的例子:

Revenue Red = CALCULATE([Revenue], 'Product'[Color] = "Red")
Revenue Red or Blue = CALCULATE([Revenue], 'Product'[Color] IN {"Red", "Blue"})
Revenue Expensive Products = CALCULATE([Revenue], 'Product'[List Price] > 1000)

表篩選器

表篩選器是一個表,可以直接用一個數據表充當表篩選器,但更常見的是用一個函數去創建表篩選器,最常用的函數就是FILTER函數。這是一個迭代器函數,有兩個入參:表和過濾條件,申明如下

FILTER(<table>,<filter>)

FILTER函數會返回一個表,結構和傳入的表一模一樣,這個返回表的數據是經過filter表達式運算后為true的那些行。

比如下面這樣,FILTER函數的返回結果就是Product表中所有滿足售價大余成本2倍的行。

Revenue High Margin Products =
CALCULATE(
	[Revenue],
	FILTER(
		'Product',
		'Product'[List Price] > 'Product'[Standard Cost] * 2
	)
)

把這個表篩選器用於CALCULATE函數,Revenue的計算就是在篩選出來的Product行數據組成的Filter Context下計算的。

其實所有的過濾器最終都會化成表過濾器,即使是bool過濾器,在pbi的內部,也會轉為表過濾器,bool過濾器可以看做是一種語法糖,只是為了便於使用。比如上面的bool過濾器例子,其實會轉為一下表過濾器:

Revenue Red =
CALCULATE(
	[Revenue],
	FILTER(
		'Product',
		'Product'[Color] = "Red"
	)
)

篩選器表達式與Filter Context的關系

一共有兩種情況:

  • 如果Filter Context已經有了某一列(或者某個表)的約束條件,那么篩選器會直接覆蓋掉這個列(或者表)的條件。
  • 如果Filter Context沒有了某一列(或者某個表)的約束條件,那么篩選器會把這列(或者表)的篩選條件加上。

比如上面寫的Revenue Red度量,只計算紅色的收入,那如果拖入顏色和Revenue Red,可以看到所有行的Revenue Red都是一樣的,這是在Filter Context中因為發生了Color列上的條件覆蓋,篩選器覆蓋了行維度。

篩選器修改函數

除了寫死篩選器求覆蓋Filter Context中的條件,還可以用修改函數去修改Filter Context。

REMOVEFILTERS 可以刪除Filter Context中的過濾條件,它可以從一個或多個列或從單個表的所有列中刪除篩選器。在算總小計的時候,這個修改器函數很有用

KEEPFILTERS 可以在保留已有篩選條件的基礎上增加新的篩選條件,比如把它用在Revenue Red中

Revenue Red =
CALCULATE(
    [Revenue],
    KEEPFILTERS('Product'[Color] = "Red")
)

那么其他顏色行的數據就會為空,只有Red行有數據

原因是行維度的值作為Filter Context的條件和表達式中的條件是and關系,所以只有Color='Red' and Color='Red'為true,其他的行都是false。

USERELATIONSHIP是個非常有特色的函數,因為它體現了一個Power BI數據建模中的一個隱藏限制:表之間的關聯關系只能是單個字段的關聯,但是允許有多組關聯關系(都是單字段關聯),但只有其中一組為活躍關聯,其他為非活躍關聯。 如果要引用非活躍關聯關系,那么就必須用USERELATIONSHIP函數指定一個非活躍關聯。

Revenue Shipped =
CALCULATE (
	[Revenue],
	USERELATIONSHIP('Date'[DateKey], Sales[ShipDateKey])
)

CROSSFILTER 能夠影響關聯關系的影響方向(維表到事實表,事實表到維表),甚至刪除關聯關系,更強大。

快捷計算

為了減輕寫dax的難度,pbi提供了快捷方式創建度量,分成以下幾類

分類聚合

分類聚合其實就是聚合的聚合,有下面這些

過濾

時間智能

前提:

必須要有時間表,時間表是一個連續的時間字段,且數據跨度滿1年。

限制:

不支持自定義財年,否則需要自己寫復雜的過濾函數。

同環比計算

Revenue YoY % =
VAR RevenuePriorYear = CALCULATE([Revenue], SAMEPERIODLASTYEAR('Date'[Date]))
RETURN
	DIVIDE(
		[Revenue] - RevenuePriorYear,
		RevenuePriorYear
	)

xtd計算

Revenue YTD =
TOTALYTD([Revenue], 'Date'[Date], "6-30")

計算拉新數

New Customers =
VAR CustomersLTD =
	CALCULATE(
		DISTINCTCOUNT(Sales[CustomerKey]),
		DATESBETWEEN(
			'Date'[Date],
			BLANK(),
			MAX('Date'[Date])
		),
		'Sales Order'[Channel] = "Internet"
	)
VAR CustomersPrior =
	CALCULATE(
		DISTINCTCOUNT(Sales[CustomerKey]),
		DATESBETWEEN(
			'Date'[Date],
			BLANK(),
			MIN('Date'[Date]) - 1
		),
		'Sales Order'[Channel] = "Internet"
	)
RETURN
	CustomersLTD - CustomersPrior

快照計算(庫存等等,其實就是最后一天聚合)

Stock on Hand =
CALCULATE(
	SUM(Inventory[UnitsBalance]),
	LASTDATE('Date'[Date])
)

度量嵌套

在定義一個度量的DAX中,可以引用其他已經存在的度量,還能創建變量,這就使得DAX非常復雜了,像一門編程語言。

和LOD區別

DAX是基於context的,LOD是基於聚合的。舉個例子,計算每個商品類型的平均售價。

DAX

在DAX中,可以直接創建 price average per product_type這樣一個度量。

price average per product_type = 
AVERAGEX(
	KEEPFILTERS(VALUES('quickbi_test company_sales_record'[product_type])),
	CALCULATE(SUM('quickbi_test company_sales_record'[price]))
)

如果不想編輯,還可以通過快捷按鈕方式直接創建常見的度量。

將度量拖到交叉表中,這時候PowerBI會根據row context,在表格上的每行都做一次品類平均值的計算。比如我們把area維度和度量price average per product_type拖入交叉表中,這時候row context是區域的值,比如第一行,就是在area=東北這個條件下,計算商品類型的售價均值。注意,度量的聚合方式是不可以更改的。

LOD

再看在LOD中,我們沒有辦法直接創建一個 price average per product_type度量,但是我們可以創建按照product_type聚合的計算字段,然后在使用該計算字段時,選擇average聚合。

首先,創建 擴展 product_type聚合的計算字段sum price per product_type:

{INCLUDE [product_type]:SUM([price])}

然后,把這個計算字段拖到交叉表中,並且選擇 average聚合:

這里的計算邏輯就是:

首先,根據area和product_type兩個維度做sum聚合。

然后,因為聚合的維度比圖表上展示的多(也就是LOD聚合粒度比圖表聚合粒度更細),這樣每行下面會有多個聚合值,所以需要進行二次聚合,這時候根據所選的聚合方式average再聚合一次。

最終結果就是在每個區域下計算商品類型的售價均值,和DAX異曲同工。

可以看到,結果和在PowerBI中用dax創建的price average per product_type是一樣的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM