你是否常在同一份報表裡 一再複製貼上同一段 DAX 邏輯?例如:
- 銷售額、銷售量、客單價都要算 去年同期,你就得重寫三次相似的語法;
- 上述三項指標都要設定 條件式變色,又得再寫三次幾乎一樣的規則。
這篇文章帶你用 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 報表無限的「複製貼上」並升級到「一次封裝、處處可用」,這篇就是你的完整指南。
- 擺脫用 Excel 製作重複性報表的無限輪迴
- 克服用 Excel 只能存一百萬列資料之限制
- 徹底理解 Power BI 與 DAX 函數底層邏輯
- 了解學習 Power BI 在就業市場的競爭力
我們還會送你一套《DAX 函數從零到壹學習藍圖》!萃取數十個常用函數,幫助你學習有方向!
立即報名免費課程,領取藍圖!
什麼是 UDF?為什麼需要用到它?
UDF 是 User-Defined Functions 的簡稱,中文可稱為「自訂函數」是微軟於 2025 年 9 月釋出的 DAX 函數新功能。
一句話總結的話就是:把你常用的 DAX 邏輯封裝成「可重用的函數」,之後在同一份模型裡的 量值(Measure)、計算資料行(Calculated Column)、視覺效果計算(Visual Calculation)甚至其它 UDF 都能直接呼叫。
範例 1:加上消費稅
舉例來說,假設在銷售額案例裡,所有定價都需要加上 10% 來反映消費稅。
我們就可以寫一個自定義函數 AddTax:
- DEFINE
- /// AddTax 接收金額參數並傳回含稅金額
- FUNCTION AddTax =
- ( amount : NUMERIC ) =>
- amount * 1.1
- EVALUATE
- { AddTax ( 10 ) }
- // 回傳 11
接著,就可以把它提供給量值使用:

其實,本質上它就是一個函數,就像是 SUMX 一樣,但只是由我們自己定義。
範例 2:去年同期(SPLY)與 年度成長率 YoY%
過去,在同一份報表裡,我們時常需要算 銷售額、銷售量、客單價三項指標的 1) 去年同期(SPLY)、2) 年度成長率 YoY%、3) 條件式變色。
在沒有自訂函數的情況下,我們需要先如此定義各別的量值:
1. 去年同期
- -- 去年同期 SPLY
- 銷售額_SPLY = CALCULATE ( [銷售額], SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )
- 銷售量_SPLY = CALCULATE ( [銷售量], SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )
- 客單價_SPLY = CALCULATE ( [客單價], SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )
2. 年度成長率 YoY%
- -- 年度成長率 YoY%
- 銷售額_YoY% = DIVIDE ( [銷售額] - [銷售額_SPLY], [銷售額_SPLY] )
- 銷售量_YoY% = DIVIDE ( [銷售量] - [銷售量_SPLY], [銷售量_SPLY] )
- 客單價_YoY% = DIVIDE ( [客單價] - [客單價_SPLY], [客單價_SPLY] )
3. 條件式變色
- -- 條件式變色
- 銷售額_YoY%_Color = SWITCH ( TRUE(), [銷售額_YoY%] > 0, "#63AD36", [銷售額_YoY%] < 0, "#DB5151", BLANK() )
- 銷售量_YoY%_Color = SWITCH ( TRUE(), [銷售量_YoY%] > 0, "#63AD36", [銷售量_YoY%] < 0, "#DB5151", BLANK() )
- 客單價_YoY%_Color = SWITCH ( TRUE(), [客單價_YoY%] > 0, "#63AD36", [客單價_YoY%] < 0, "#DB5151", BLANK() )
你可以發現,無論是去年同期、年度成長率 YoY%、條件式變色,每一組裡面的寫法其實都很相似。
如果今天邏輯改變了,例如:條件式變色改顏色了,那我們就需要改三組公式,很難維護。
因此,我們可以把這些相似的邏輯萃取出來。
1. 去年同期
- /// 去年同期值(SPLY)
- FUNCTION SPLY =
- ( m : Scalar Numeric expr ) =>
- CALCULATE ( m, SAMEPERIODLASTYEAR( 'Calendar'[Date] ) )
2. 年度成長率 YoY%
- /// 通用 YoY%
- FUNCTION YoYPercent =
- ( m : Scalar Numeric expr ) =>
- VAR prev = SPLY(m)
- RETURN DIVIDE ( m - prev, prev )
3. 條件式變色
- /// 條件式變色
- FUNCTION YoYColor =
- ( pct : NUMERIC ) =>
- SWITCH ( TRUE(), ISBLANK(pct), BLANK(), pct > 0, "#63AD36", pct < 0, "#DB5151" )
當我們把公式定義好以後,若要算三個指標的 YoY%,量值就可以寫成:
- 銷售額_YoY% = YoYPercent( [銷售額] )
- 銷售量_YoY% = YoYPercent( [銷售量] )
- 客單價_YoY% = YoYPercent( [客單價] )
條件式變色也可以寫得很簡潔:
- 銷售額_YoY%_Color = YoYColor( [銷售額_YoY%] )
- 銷售量_YoY%_Color = YoYColor( [銷售量_YoY%] )
- 銷售價_YoY%_Color = YoYColor( [銷售價_YoY%] )
之後不管是去年同期邏輯要換、或顏色門檻要調整,你只需要改對應的自訂函數一處,整個檔案會跟著正確(而不是像以前要改三次)。
2 種定義 UDF 的方法
啟用預覽功能
首先,截至本篇文章撰寫的當下(2025 年 9 月),此功能還僅是 Power BI 預覽功能,需要我們自行啟用。
這步驟只需要做一次,以後便不需要再做。啟用步驟如下:

- 點擊畫面右下角的齒輪符號
- 切換到「預覽功能」
- 選取「DAX 使用者定義的函式」
- 點擊「確定」
按下確定以後,Power BI 會要我們重新開啟。按照指示關掉軟體後再重開即可。
利用 DAX 查詢檢視定義
步驟 1:切換到模型檢視
第一種定義的方式是利用「DAX 查詢檢視」來定義,如下圖。

步驟 2:定義函數、結構介紹
定義 UDF 函數會須要兩個關鍵字:DEFINE 與 FUNCTION,如下圖中標註的 1、2。

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

- 函數的名稱
- 函數的參數與其型別
- 函數的邏輯
其中,第 6 點函數的邏輯區塊,雖然此處只有簡單的 amount * 1.1。但是,這邊實際上可以寫無限長的邏輯。
除此之外,邏輯的前後我習慣會加上大括號框住 {}。這不是必須,但是我寫程式的習慣,看起來會更有結構。
如果你會寫 JavaScript 或 TypeScript,應該會發現 UDF 寫法與 JavaScript 或 TypeScript 的函數寫法極其相似。
步驟 3:新增函數到資料模型中
當函數完成撰寫以後,有兩個方式可以新增函數到模型中。
如下圖,我們可以按「使用變更更新模型」或「更新模型:新增新函式」。

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

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

TMDL 檢視新增方式與 DAX 查詢檢視新增方式相似,只是關鍵字變成是 createOrReplace 與 function。
除此之外 TMDL 對於函數寫法更加嚴謹。
「換行」、「空格」、「縮排」等稍有不慎,便會出現錯誤無法新增。因此,我個人是比較喜歡使用 DAX 查詢檢視。
4 個在 Power BI 中使用 UDF 的地方
將自訂函數定義好並儲存到資料模型後,我們可以從以下四個地方使用它:
- 量值(Measure)
- 計算資料行(Calculated Column)
- 視覺效果計算(Visual Calculation)
- 別的 UDF
這裡延續我們前面的簡單範例 AddTax 函數,逐一示範四種用法。
在開始以前,假設模型中已經有一個 [銷售額] 量值,邏輯如下:
- 銷售額 =
- SUMX (
- Sales,
- Sales[SalesQuantity] * Sales[UnitPrice] - Sales[DiscountAmount]
- )
在量值(Measure)中使用
我們可以新增一個 量值 計算加上稅金以後的銷售額,即含稅銷售額:
- 含稅銷售額 = AddTax ( [銷售額] )
接著,便可以在視覺效果中使用:

利用量值呼叫自訂函數會保持量值的特徵:隨著報表篩選器條件即時動態變化。
在計算資料行(Calculated Column)中使用
我們也可以利用 計算資料行 計算含稅銷售額:
- 含稅銷售額 =
- CONVERT (
- AddTax ( 'Sales'[SalesAmount] ),
- CURRENCY
- )

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

利用計算資料行呼叫 UDF 函數產生的資料行會是靜態不改變的。
若需要更多動態篩選功能,需考慮使用量值。
在視覺效果計算(Visual Calculation)中使用
若不想額外新增量值或計算資料行,並且邏輯只在單一視覺效果會用到的話,則可以用視覺效果計算的方式新增:
- 含稅銷售額 = AddTax ( [銷售額] )

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

視覺效果計算只能存取該視覺中的欄位/量值,不會跨視覺效果、也不會寫回模型。 很適合快速試算或在報表端做小調整。
在 UDF 中呼叫 UDF
在以下案例中,延續前文中所述的 AddTax,並在另一個自訂函數 AddTaxAndDiscount 中呼叫它。
- DEFINE
- /// AddTax 接收金額並傳回含稅金額
- FUNCTION AddTax =
- ( amount : NUMERIC ) =>
- amount * 1.1
- FUNCTION AddTaxAndDiscount =
- (
- amount : NUMERIC,
- discount : NUMERIC
- ) =>
- AddTax ( amount - discount )
- EVALUATE
- { AddTaxAndDiscount ( 10, 2 ) }
- // 回傳 8.8
快速選擇指南
介紹完以上四種使用的方式以後,你可能會好奇到底該用哪一種?
- 需要跟著篩選器作條件動態變化 → 用 量值(Measure)
- 需要做資料預先轉換,結果固定 → 用 計算資料行(Calculated Column)
- 只在某張圖表上臨時計算 → 用 視覺效果計算(Visual Calculation)
- 邏輯會重用/會演進 → UDF in UDF,把小邏輯抽出再組合
參數與型別
一個完整的 UDF 可以接受零個或多個參數。
而每一個參數,我們可以指定參數的「型別提示」,位置依序是:[type] [subtype] [parameterMode],如下:
- DEFINE
- /// 這裡可以寫函數的註解(功能、用途等等)
- FUNCTION fnThisIsAUdf = ( param : [type] [subtype] [parameterMode] ) => {
- param * 1.1
- }
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 參數指定後,第三個位置可以使用 val 或 expr。
如果我們希望純量在上下文中只被求值一次,使用 val。
如果我們希望延遲求值並可能在函數內部套用上下文,則使用 expr。
若第一個位置指定為 AnyRef,則此處必須是 expr,因為其需要在函數的上下文中進行求值。
型別定義範例 1
- DEFINE
- /// 回傳 x 並轉為 Int64 型別
- FUNCTION CastToInt = (
- x : SCALAR INT64 VAL
- ) =>
- x
- EVALUATE
- { CastToInt ( 3.4 ), CastToInt ( 3.5 ), CastToInt ( "5" ) }
- // 回傳 3, 4, 5
這使用了 SCALAR 類型、Int64 子類型和 val 參數模式來 立即求值 正整數數值。
我們也可以透過僅包含 Int64 子類型來實現這一點,如下例所示:
- DEFINE
- /// 回傳 x 並轉為 Int64 型別
- FUNCTION CastToInt = (
- x : INT64
- ) =>
- x
- EVALUATE
- { CastToInt ( 3.4 ), CastToInt ( 3.5 ), CastToInt ( "5" ) }
- // 回傳 3, 4, 5
型別定義範例 2
為了說明 UDF parameterMode 如何受 Filter Context 影響,以下兩個案例均是計算 Sales 資料表總共有幾列。
唯一的差異是一個參數宣告為 val(立即求值),另一個參數宣告為 expr(延遲求值)。
- DEFINE
- /// Table val: 接受一個 materialized 資料表,上下文不能改變
- FUNCTION fnCountRowsNow = ( t : TABLE VAL ) => {
- COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Calendar' ) ) )
- }
- /// Table expr: 接受一個表達式,上下文能改變
- FUNCTION fnCountRowsLater = ( t : TABLE EXPR ) => {
- COUNTROWS ( CALCULATETABLE ( t, ALL ( 'Calendar' ) ) )
- }
- EVALUATE
- ROW (
- "Now_val", CALCULATE ( fnCountRowsNow ( 'Sales' ), 'Calendar'[Year] = 2013 ),
- "Later_expr",CALCULATE ( fnCountRowsLater ( 'Sales' ), 'Calendar'[Year] = 2013 )
- )
- // 回傳 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 公式

- DEFINE
- FUNCTION fnSetBackgroundColor1 = (pct) => {
- SWITCH (
- TRUE (),
- pct = 0, BLANK (),
- pct > 0, "#63AD36",
- pct < 0, "#DB5151"
- )
- }
案例 2:封裝累加計算的 DAX 公式

- DEFINE FUNCTION fnGetRunningTotal1 = (val) => {
- CALCULATE (
- val,
- FILTER (
- ALL ( 'Calendar' ),
- 'Calendar'[DateInt] <= MAX ( 'Calendar'[DateInt] ) &&
- 'Calendar'[Year] = SELECTEDVALUE ( 'Calendar'[Year] )
- )
- )
- }
- FUNCTION fnGetRunningTotal2 = (val: expr) => {
- CALCULATE (
- val,
- FILTER (
- ALL ( 'Calendar' ),
- 'Calendar'[DateInt] <= MAX ( 'Calendar'[DateInt] ) &&
- 'Calendar'[Year] = SELECTEDVALUE ( 'Calendar'[Year] )
- )
- )
- }
案例 3:利用 UDF 封裝 UDF 函數邏輯

- DEFINE FUNCTION fnGetValueSPLY = (val: expr) => {
- CALCULATE (
- val,
- SAMEPERIODLASTYEAR ( 'Calendar'[DateKey] )
- )
- }
- FUNCTION fnGetYoYPct = (val: expr) => {
- VAR valSPLY = fnGetValueSPLY ( val )
- RETURN
- DIVIDE (
- val - valSPLY,
- valSPLY
- )
- }
結語:把重複的 DAX,升級成可重用的「標準零件」
看到這裡,相信你已經對 UDF 有更深一步的認識:為什麼需要、怎麼啟用、在哪裡使用(量值 / 計算資料行 / 視覺效果計算 / UDF in UDF)、以及參數型別與 val vs expr 的差異。
更重要的是,還有我額外提供你的 3 個實戰情境,讓你知道使用它不只是語法的提升,而是能在 Power BI 報表裡直接省時、降錯、維持一致性的做法。
如果你的日常還在為「去年同期、YoY%、累加」這些邏輯重複修修補補,現在就把它們抽成 UDF:
- 規則改一次、全檔同步;
- 任何量值都能共用;
- 團隊交接只要認得幾顆核心函數,學習成本低很多。
歡迎在留言告訴我:你還想把哪些 DAX 邏輯寫成自訂函數呢?
讓我們一起把報表從「複製貼上」升級到「一次封裝、處處可用」吧!
關於 Stark
是一名在科技業賣肝的軟體工程師,協助企業資料視覺化。
致力於分享 Power BI 知識與技術,讓資料擁有無限可能。
關注我的 Instagram 獲得更即時資訊:Stark@I Master Power BI。
Power BI 基礎知識
- 【 時間篩選為何無效? 】2個案例解析 Power BI 日期維度表的重要性!
- 【 Power BI Desktop vs Service vs Report Server 】功能差異、應用場景與部署指南
- 【 5 個 Power BI 命名原則 】提高報表可讀性與可維護性
- 【 Power BI 教學資源分享 】15 個新手必知的教學資源
- 【 Power BI 是什麼 】3 種適合使用的人|4 大功能|Power BI vs. Excel vs. Python



