DAX UDF 完整指南:3 個 Power BI 實戰案例|4 個使用 UDF 的地方

你是否常在同一份報表裡 一再複製貼上同一段 DAX 邏輯?例如:

  1. 銷售額、銷售量、客單價都要算 去年同期,你就得重寫三次相似的語法;
  2. 上述三項指標都要設定 條件式變色,又得再寫三次幾乎一樣的規則。

這篇文章帶你用 DAX UDF(User-Defined Functions) 把常用邏輯封裝成「可重用函數」。

並且可以將它套用在 量值(Measure)計算資料行(Calculated Column)視覺效果計算(Visual Calculation),不只更乾淨,也更容易維護。

透過這篇文章,你將快速學到:

✅ UDF 是什麼?為什麼需要用到它?

✅ 2 種定義 UDF 的方法

✅ 4 個在 Power BI 中使用 UDF 的地方

✅ 參數與型別

✅ Bonus:3 個 UDF 的實戰案例
⠀⠀1️⃣ 封裝格式化底色邏輯
⠀⠀2️⃣ 封裝累加計算邏輯
⠀⠀3️⃣ 封裝 UDF 函數邏輯

此外,文末我還會額外提供你 3 個函數實戰案例的影片教學,手把手帶你真正地學會這個新功能。

如果你想擺脫 Power BI 報表無限的「複製貼上」並升級到「一次封裝、處處可用」,這篇就是你的完整指南。

【🆓 免費資源】從零開始,理解 Power BI 與 DAX 函數底層邏輯 🚀
如果你想:
  • 擺脫用 Excel 製作重複性報表的無限輪迴
  • 克服用 Excel 只能存一百萬列資料之限制
  • 徹底理解 Power BI 與 DAX 函數底層邏輯
  • 了解學習 Power BI 在就業市場的競爭力
歡迎點擊下方按鈕,報名免費課程。
我們還會送你一套《DAX 函數從零到壹學習藍圖》!萃取數十個常用函數,幫助你學習有方向!
免費 Power BI & DAX 課程 立即報名免費課程,領取藍圖!
—— Stark:雖然是免費課程,但內容絕對不馬虎,推薦你找時間好好看完!

什麼是 UDF?為什麼需要用到它?

UDF 是 User-Defined Functions 的簡稱,中文可稱為「自訂函數」是微軟於 2025 年 9 月釋出的 DAX 函數新功能。

一句話總結的話就是:把你常用的 DAX 邏輯封裝成「可重用的函數」,之後在同一份模型裡的 量值(Measure)、計算資料行(Calculated Column)、視覺效果計算(Visual Calculation)甚至其它 UDF 都能直接呼叫。

範例 1:加上消費稅

舉例來說,假設在銷售額案例裡,所有定價都需要加上 10% 來反映消費稅。

我們就可以寫一個自定義函數 AddTax

DAX UDF — AddTax
  1. DEFINE
  2. /// AddTax 接收金額參數並傳回含稅金額
  3. FUNCTION AddTax =
  4. ( amount : NUMERIC ) =>
  5. amount * 1.1
  6. EVALUATE
  7. { AddTax ( 10 ) }
  8. // 回傳 11

接著,就可以把它提供給量值使用:

其實,本質上它就是一個函數,就像是 SUMX 一樣,但只是由我們自己定義。

範例 2:去年同期(SPLY)與 年度成長率 YoY%

過去,在同一份報表裡,我們時常需要算 銷售額、銷售量、客單價三項指標的 1) 去年同期(SPLY)、2) 年度成長率 YoY%、3) 條件式變色

在沒有自訂函數的情況下,我們需要先如此定義各別的量值:

1. 去年同期

DAX 範例 — 去年同期 (SPLY)
  1. -- 去年同期 SPLY
  2. 銷售額_SPLY = CALCULATE ( [銷售額], SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )
  3. 銷售量_SPLY = CALCULATE ( [銷售量], SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )
  4. 客單價_SPLY = CALCULATE ( [客單價], SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )

2. 年度成長率 YoY%

DAX 範例 — 年度成長率 (YoY%)
  1. -- 年度成長率 YoY%
  2. 銷售額_YoY% = DIVIDE ( [銷售額] - [銷售額_SPLY], [銷售額_SPLY] )
  3. 銷售量_YoY% = DIVIDE ( [銷售量] - [銷售量_SPLY], [銷售量_SPLY] )
  4. 客單價_YoY% = DIVIDE ( [客單價] - [客單價_SPLY], [客單價_SPLY] )

3. 條件式變色

DAX 範例 — 條件式變色
  1. -- 條件式變色
  2. 銷售額_YoY%_Color = SWITCH ( TRUE(), [銷售額_YoY%] > 0, "#63AD36", [銷售額_YoY%] < 0, "#DB5151", BLANK() )
  3. 銷售量_YoY%_Color = SWITCH ( TRUE(), [銷售量_YoY%] > 0, "#63AD36", [銷售量_YoY%] < 0, "#DB5151", BLANK() )
  4. 客單價_YoY%_Color = SWITCH ( TRUE(), [客單價_YoY%] > 0, "#63AD36", [客單價_YoY%] < 0, "#DB5151", BLANK() )

你可以發現,無論是去年同期、年度成長率 YoY%、條件式變色,每一組裡面的寫法其實都很相似。

如果今天邏輯改變了,例如:條件式變色改顏色了,那我們就需要改三組公式,很難維護

因此,我們可以把這些相似的邏輯萃取出來。

1. 去年同期

DAX UDF — SPLY(去年同期值)
  1. /// 去年同期值(SPLY)
  2. FUNCTION SPLY =
  3. ( m : Scalar Numeric expr ) =>
  4. CALCULATE ( m, SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )

2. 年度成長率 YoY%

DAX UDF — YoYPercent(通用 YoY%)
  1. /// 通用 YoY%
  2. FUNCTION YoYPercent =
  3. ( m : Scalar Numeric expr ) =>
  4. VAR prev = SPLY(m)
  5. RETURN DIVIDE ( m - prev, prev )

3. 條件式變色

DAX UDF — YoYColor(條件式變色)
  1. /// 條件式變色
  2. FUNCTION YoYColor =
  3. ( pct : NUMERIC ) =>
  4. SWITCH ( TRUE(), ISBLANK(pct), BLANK(), pct > 0, "#63AD36", pct < 0, "#DB5151" )

當我們把公式定義好以後,若要算三個指標的 YoY%,量值就可以寫成:

DAX — 套用 YoYPercent UDF
  1. 銷售額_YoY% = YoYPercent( [銷售額] )
  2. 銷售量_YoY% = YoYPercent( [銷售量] )
  3. 客單價_YoY% = YoYPercent( [客單價] )

條件式變色也可以寫得很簡潔:

DAX — 套用 YoYColor UDF(條件式變色)
  1. 銷售額_YoY%_Color = YoYColor( [銷售額_YoY%] )
  2. 銷售量_YoY%_Color = YoYColor( [銷售量_YoY%] )
  3. 銷售價_YoY%_Color = YoYColor( [銷售價_YoY%] )

之後不管是去年同期邏輯要換、或顏色門檻要調整,你只需要改對應的自訂函數一處,整個檔案會跟著正確(而不是像以前要改三次)。

2 種定義 UDF 的方法

啟用預覽功能

首先,截至本篇文章撰寫的當下(2025 年 9 月),此功能還僅是 Power BI 預覽功能,需要我們自行啟用。

這步驟只需要做一次,以後便不需要再做。啟用步驟如下:

  1. 點擊畫面右下角的齒輪符號
  2. 切換到「預覽功能」
  3. 選取「DAX 使用者定義的函式」
  4. 點擊「確定」

按下確定以後,Power BI 會要我們重新開啟。按照指示關掉軟體後再重開即可。

利用 DAX 查詢檢視定義

步驟 1:切換到模型檢視

第一種定義的方式是利用「DAX 查詢檢視」來定義,如下圖。

步驟 2:定義函數、結構介紹

定義 UDF 函數會須要兩個關鍵字:DEFINEFUNCTION,如下圖中標註的 1、2。

FUNCTION 關鍵字以後便開始定義函數,詳細如下圖:

  1. 函數的名稱
  2. 函數的參數與其型別
  3. 函數的邏輯

其中,第 6 點函數的邏輯區塊,雖然此處只有簡單的 amount * 1.1。但是,這邊實際上可以寫無限長的邏輯。

除此之外,邏輯的前後我習慣會加上大括號框住 {}。這不是必須,但是我寫程式的習慣,看起來會更有結構。

如果你會寫 JavaScript 或 TypeScript,應該會發現 UDF 寫法與 JavaScript 或 TypeScript 的函數寫法極其相似。

步驟 3:新增函數到資料模型中

當函數完成撰寫以後,有兩個方式可以新增函數到模型中。

如下圖,我們可以按「使用變更更新模型」或「更新模型:新增新函式」。

這時候我們會在畫面右邊資料面板的「模型」中,「函式」處發現我們新增的函數。

利用 TMDL 查詢檢視定義

第二種新增的方式是利用「TMDL 檢視」,如下圖。

TMDL 檢視新增方式與 DAX 查詢檢視新增方式相似,只是關鍵字變成是 createOrReplacefunction

除此之外 TMDL 對於函數寫法更加嚴謹。

「換行」、「空格」、「縮排」等稍有不慎,便會出現錯誤無法新增。因此,我個人是比較喜歡使用 DAX 查詢檢視。

4 個在 Power BI 中使用 UDF 的地方

將自訂函數定義好並儲存到資料模型後,我們可以從以下四個地方使用它:

  1. 量值(Measure)
  2. 計算資料行(Calculated Column)
  3. 視覺效果計算(Visual Calculation)
  4. 別的 UDF

這裡延續我們前面的簡單範例 AddTax 函數,逐一示範四種用法。

在開始以前,假設模型中已經有一個 [銷售額] 量值,邏輯如下:

DAX — 銷售額(SUMX)
  1. 銷售額 =
  2. SUMX (
  3. Sales,
  4. Sales[SalesQuantity] * Sales[UnitPrice] - Sales[DiscountAmount]
  5. )

在量值(Measure)中使用

我們可以新增一個 量值 計算加上稅金以後的銷售額,即含稅銷售額:

DAX — 含稅銷售額(AddTax 版)
  1. 含稅銷售額 = AddTax ( [銷售額] )

接著,便可以在視覺效果中使用:

利用量值呼叫自訂函數會保持量值的特徵:隨著報表篩選器條件即時動態變化。

在計算資料行(Calculated Column)中使用

我們也可以利用 計算資料行 計算含稅銷售額:

DAX — 含稅銷售額(CONVERT + AddTax)
  1. 含稅銷售額 =
  2. CONVERT (
  3. AddTax ( 'Sales'[SalesAmount] ),
  4. CURRENCY
  5. )

接著,便可以在視覺效果中使用:

利用計算資料行呼叫 UDF 函數產生的資料行會是靜態不改變的。
若需要更多動態篩選功能,需考慮使用量值。

在視覺效果計算(Visual Calculation)中使用

若不想額外新增量值或計算資料行,並且邏輯只在單一視覺效果會用到的話,則可以用視覺效果計算的方式新增:

DAX — 含稅銷售額(AddTax 版)
  1. 含稅銷售額 = AddTax ( [銷售額] )

視覺效果上的計算結果會是一模一樣的:

視覺效果計算只能存取該視覺中的欄位/量值,不會跨視覺效果、也不會寫回模型。 很適合快速試算或在報表端做小調整。

在 UDF 中呼叫 UDF

在以下案例中,延續前文中所述的 AddTax,並在另一個自訂函數 AddTaxAndDiscount 中呼叫它。

DAX UDF — AddTax / AddTaxAndDiscount
  1. DEFINE
  2. /// AddTax 接收金額並傳回含稅金額
  3. FUNCTION AddTax =
  4. ( amount : NUMERIC ) =>
  5. amount * 1.1
  6. FUNCTION AddTaxAndDiscount =
  7. (
  8. amount : NUMERIC,
  9. discount : NUMERIC
  10. ) =>
  11. AddTax ( amount - discount )
  12. EVALUATE
  13. { AddTaxAndDiscount ( 10, 2 ) }
  14. // 回傳 8.8

快速選擇指南

介紹完以上四種使用的方式以後,你可能會好奇到底該用哪一種?

  • 需要跟著篩選器作條件動態變化 → 用 量值(Measure)
  • 需要做資料預先轉換,結果固定 → 用 計算資料行(Calculated Column)
  • 只在某張圖表上臨時計算 → 用 視覺效果計算(Visual Calculation)
  • 邏輯會重用/會演進UDF in UDF,把小邏輯抽出再組合

參數與型別

一個完整的 UDF 可以接受零個或多個參數。

而每一個參數,我們可以指定參數的「型別提示」,位置依序是:[type] [subtype] [parameterMode],如下:

DAX UDF — fnThisIsAUdf(型別註解示意)
  1. DEFINE
  2. /// 這裡可以寫函數的註解(功能、用途等等)
  3. FUNCTION fnThisIsAUdf = ( param : [type] [subtype] [parameterMode] ) => {
  4. param * 1.1
  5. }

Type(類型,參數型別的第一個位置)

Type 定義了參數接受的型別,主要分做兩大類:數值(value)或 表達式(expression)

數值(value)型別

這類型的參數會在傳遞到 UDF 以前 就算完結果,並將結果傳到函數內。

換句話說,UDF 內的邏輯並不會改變參數數值運算的結果

這類型的參數型別可以指定為:

  • AnyVal:預設的型別;可以接受純量數值(scalar)或資料表(table)。
  • Scalar:純量數值(scalar)型別,可以在 [subtype] 中進一步定義更細的型別。
  • Table:資料表(table)型別。

表達式(expression)型別

這類型的參數會在傳遞到 UDF 才會算數值。特別適合接受量值作為參數傳入的狀況。

僅可以指定一種,即:

  • AnyRef:可以引用資料行、資料表、日期表、量值。

Subtype(子類型,參數型別的第二個位置)

Subtype 可定義特定的純量資料型別。

如果定義了 Subtype,則無需明確在第一個位置將參數定義為 Scalar,系統會自動識別。

Subtype 包括:

  • Variant:接受任何純量。
  • Int64:接受整數。
  • Decimal:接受固定小數位數的小數(例如 Currency 或 Money)。`
  • Double:接受浮點小數。
  • String:接受文字。
  • DateTime:接受日期/時間。
  • Boolean:接受 TRUE/FALSE。`
  • Numeric:接受任何數值(Int64、Decimal 或 Double 的子類型)。

ParameterMode(參數模式,參數型別的第三個位置)

ParameterMode 控制了參數的數值在何時與何處被計算,主要有兩種:val expr

val(立即求值,Eager Evaluation)

在呼叫函數之前,會先求值一次,然後將結果值傳入。

這通常用於簡單的純量(scalar)或資料表(table)輸入。

如果定義函數的時候省略不寫 parameterMode,則預設為此模式。

expr(延遲求值,Lazy Evaluation)

表達式在函數內部才會求值,不會在外部就先把數值算出來。

特別是當計算時要考慮 Row Context 或 Filter Context 時非常有用。

與第一個位置的關係

第一個位置的 Scalar 參數指定後,第三個位置可以使用 valexpr

如果我們希望純量在上下文中只被求值一次,使用 val

如果我們希望延遲求值並可能在函數內部套用上下文,則使用 expr

若第一個位置指定為 AnyRef,則此處必須是 expr,因為其需要在函數的上下文中進行求值。

型別定義範例 1

DAX UDF — CastToInt(SCALAR INT64 VAL)
  1. DEFINE
  2. /// 回傳 x 並轉為 Int64 型別
  3. FUNCTION CastToInt = (
  4. x : SCALAR INT64 VAL
  5. ) =>
  6. x
  7. EVALUATE
  8. { CastToInt ( 3.4 ), CastToInt ( 3.5 ), CastToInt ( "5" ) }
  9. // 回傳 3, 4, 5

這使用了 SCALAR 類型、Int64 子類型和 val 參數模式來 立即求值 正整數數值。

我們也可以透過僅包含 Int64 子類型來實現這一點,如下例所示:

DAX UDF — CastToInt(INT64)
  1. DEFINE
  2. /// 回傳 x 並轉為 Int64 型別
  3. FUNCTION CastToInt = (
  4. x : INT64
  5. ) =>
  6. x
  7. EVALUATE
  8. { CastToInt ( 3.4 ), CastToInt ( 3.5 ), CastToInt ( "5" ) }
  9. // 回傳 3, 4, 5

型別定義範例 2

為了說明 UDF parameterMode 如何受 Filter Context 影響,以下兩個案例均是計算 Sales 資料表總共有幾列。

唯一的差異是一個參數宣告為 val(立即求值),另一個參數宣告為 expr(延遲求值)。

DAX UDF — fnCountRowsNow / fnCountRowsLater + 測試
  1. DEFINE
  2. /// Table val: 接受一個 materialized 資料表,上下文不能改變
  3. FUNCTION fnCountRowsNow = ( t : TABLE VAL ) => {
  4. COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Calendar' ) ) )
  5. }
  6. /// Table expr: 接受一個表達式,上下文能改變
  7. FUNCTION fnCountRowsLater = ( t : TABLE EXPR ) => {
  8. COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Calendar' ) ) )
  9. }
  10. EVALUATE
  11. ROW (
  12. "Now_val", CALCULATE ( fnCountRowsNow ( 'Sales' ), 'Calendar'[Year] = 2013 ),
  13. "Later_expr",CALCULATE ( fnCountRowsLater ( 'Sales' ), 'Calendar'[Year] = 2013 )
  14. )
  15. // 回傳 604814, 2282482

fnCountRowsNow 的 Sales 資料表在傳入 UDF 以前,因為是使用 VAL 參數模式,因此已經先被篩選為年度為 2013 的銷售紀錄表('Calendar'[Year] = 2013)。

所以函數內的 ALL ( 'Calendar' ) 是不會對其產生效果。

最終回傳 2013 年的銷售筆數而已。

至於 fnCountRowsLater 會接收表格表達式並保留函數以外的篩選('Calendar'[Year] = 2013),在傳入函數以後並被 ALL ( 'Calendar' ) 覆寫篩選,從而刪除外部年份篩選。

最終回傳全部的銷售筆數。

Bonus:3 個 UDF 實戰案例

事實上,在實戰中還有很多用途。

這裡我特別提供給你一段先前我直播回放,手把手教你額外的三項 UDF 實戰應用。

P.S. 你可以前往「Power BI 新手村」這個免費社群索取操作檔案一邊操作。

案例 1:封裝控制格式化底色的 DAX 公式

DAX UDF — fnSetBackgroundColor1
  1. DEFINE
  2. FUNCTION fnSetBackgroundColor1 = (pct) => {
  3. SWITCH (
  4. TRUE (),
  5. pct = 0, BLANK (),
  6. pct > 0, "#63AD36",
  7. pct < 0, "#DB5151"
  8. )
  9. }

案例 2:封裝累加計算的 DAX 公式

DAX UDF — Running Total(年度內累計)
  1. DEFINE FUNCTION fnGetRunningTotal1 = (val) => {
  2. CALCULATE (
  3. val,
  4. FILTER (
  5. ALL ( 'Calendar' ),
  6. 'Calendar'[DateInt] <= MAX ( 'Calendar'[DateInt] ) &&
  7. 'Calendar'[Year] = SELECTEDVALUE ( 'Calendar'[Year] )
  8. )
  9. )
  10. }
  11. FUNCTION fnGetRunningTotal2 = (val: expr) => {
  12. CALCULATE (
  13. val,
  14. FILTER (
  15. ALL ( 'Calendar' ),
  16. 'Calendar'[DateInt] <= MAX ( 'Calendar'[DateInt] ) &&
  17. 'Calendar'[Year] = SELECTEDVALUE ( 'Calendar'[Year] )
  18. )
  19. )
  20. }

案例 3:利用 UDF 封裝 UDF 函數邏輯

DAX UDF — YoY 工具組(值與百分比)
  1. DEFINE FUNCTION fnGetValueSPLY = (val: expr) => {
  2. CALCULATE (
  3. val,
  4. SAMEPERIODLASTYEAR ( 'Calendar'[DateKey] )
  5. )
  6. }
  7. FUNCTION fnGetYoYPct = (val: expr) => {
  8. VAR valSPLY = fnGetValueSPLY ( val )
  9. RETURN
  10. DIVIDE (
  11. val - valSPLY,
  12. valSPLY
  13. )
  14. }

結語:把重複的 DAX,升級成可重用的「標準零件」

看到這裡,相信你已經對 UDF 有更深一步的認識:為什麼需要、怎麼啟用、在哪裡使用(量值 / 計算資料行 / 視覺效果計算 / UDF in UDF)、以及參數型別與 val vs expr 的差異。

更重要的是,還有我額外提供你的 3 個實戰情境,讓你知道使用它不只是語法的提升,而是能在 Power BI 報表裡直接省時、降錯、維持一致性的做法。

如果你的日常還在為「去年同期、YoY%、累加」這些邏輯重複修修補補,現在就把它們抽成 UDF

  • 規則改一次、全檔同步;
  • 任何量值都能共用;
  • 團隊交接只要認得幾顆核心函數,學習成本低很多。

歡迎在留言告訴我:你還想把哪些 DAX 邏輯寫成自訂函數呢?

讓我們一起把報表從「複製貼上」升級到「一次封裝、處處可用」吧!

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *