Power BI Exercise - Creating a Reusable DAX User-Defined Function
Power BI Exercise - Creating a Reusable DAX User-Defined Function
Package safe percentage logic once and call it from multiple retail measures.
In this guided exercise, you will define a reusable DAX function named SafePercentage. You will save it to the semantic model and call it from Sales, Quantity, and Target measures.
Availability requirement
DAX user-defined functions became generally available in Power BI Desktop and the Power BI Service beginning with the June 2026 release. They require semantic-model compatibility level 1702 or later.
Why create a user-defined function?
The model already contains several percentage calculations. Their numerators and denominators differ, but all of them need the same safe division behaviour:
DIVIDE (
numerator,
denominator,
alternate result
)
A user-defined function, or UDF, packages this logic and accepts the changing values as parameters.
One function centralises the division rule while measures retain their business meaning.
Before you begin
Use the completed retail model and confirm these measures are available:
[Total Sales]= 21,788[Total Quantity]= 42[Selected Sales Target]= 22,000 when no single target is selected
Step 1: Open DAX Query View
- Select the DAX Query View icon on the left side of Power BI Desktop.
- Create a new blank query tab.
- Enter the function definition and test query shown in the next step.
Step 2: Define SafePercentage
DEFINE
/// Safely divides two numeric values.
/// @param {NUMERIC} numerator - Value being compared.
/// @param {NUMERIC} denominator - Total or target value.
/// @param {NUMERIC} alternate - Result when denominator is zero.
/// @returns A numeric ratio.
FUNCTION SafePercentage = (
numerator : NUMERIC,
denominator : NUMERIC,
alternate : NUMERIC = 0
) =>
DIVIDE (
numerator,
denominator,
alternate
)
EVALUATE
ROW (
"Test 25 of 100", SafePercentage ( 25, 100 ),
"Test zero denominator", SafePercentage ( 25, 0 )
)
Select Run. The expected results are:
| Test | Expected result |
|---|---|
| 25 divided by 100 | 0.25 |
| 25 divided by zero | 0 |
Step 3: Save the function to the model
- After the test succeeds, select Update model with changes.
- Alternatively, select the Update model: Add new function action displayed above the function definition.
- Open Model explorer and expand Functions.
- Confirm that
SafePercentageappears there.
Step 4: Create Category Sales Percentage using the UDF
Category Sales % UDF =
SafePercentage (
[Total Sales],
CALCULATE (
[Total Sales],
REMOVEFILTERS ( DimProduct[Category] )
)
)
Format the measure as Percentage with two decimal places.
Step 5: Create Category Quantity Percentage
Category Quantity % UDF =
SafePercentage (
[Total Quantity],
CALCULATE (
[Total Quantity],
REMOVEFILTERS ( DimProduct[Category] )
)
)
The same SafePercentage function now works with a different base measure.
Step 6: Validate category results
Create a Table visual using DimProduct[Category] and both new measures.
| Category | Sales % UDF | Quantity % UDF |
|---|---|---|
| Computing | 27.35% | 26.19% |
| Entertainment | 27.05% | 16.67% |
| Home | 21.90% | 21.43% |
| Mobile | 23.71% | 35.71% |
| Total | 100.00% | 100.00% |
Step 7: Reuse the function for target achievement
If [Selected Sales Target] exists, create:
Target Achievement % UDF =
SafePercentage (
[Total Sales],
[Selected Sales Target]
)
| Selected target | Total Sales | Expected achievement |
|---|---|---|
| 20,000 | 21,788 | 108.94% |
| 22,000 | 21,788 | 99.04% |
| 25,000 | 21,788 | 87.15% |
Step 8: Validate measures in DAX Query View
EVALUATE
SUMMARIZECOLUMNS (
DimProduct[Category],
"Sales Percentage", [Category Sales % UDF],
"Quantity Percentage", [Category Quantity % UDF]
)
ORDER BY DimProduct[Category]
The query result grid may display raw decimals. For example, Computing Sales Percentage may appear as approximately 0.2735451.
Step 9: Inspect the saved function
Run:
EVALUATE
INFO.USERDEFINEDFUNCTIONS ()
The result includes function metadata such as its name, expression, state, and any error message. Confirm that SafePercentage has a ready state and no error.
Step 10: Test the alternate parameter
The alternate parameter is optional because its default is zero:
DEFINE
FUNCTION SafePercentageTest = (
numerator : NUMERIC,
denominator : NUMERIC,
alternate : NUMERIC = 0
) =>
DIVIDE ( numerator, denominator, alternate )
EVALUATE
ROW (
"Default alternate", SafePercentageTest ( 10, 0 ),
"Custom alternate", SafePercentageTest ( 10, 0, -1 )
)
The expected results are 0 and -1 respectively.
UDF versus Calculation Group
| Feature | DAX UDF | Calculation Group |
|---|---|---|
| Main purpose | Package parameterised logic that measures call explicitly. | Transform the measure currently in context. |
| Inputs | Accepts named parameters. | Uses functions such as SELECTEDMEASURE. |
| Application | Called by name inside a DAX expression. | Applied through a calculation item in a visual, slicer, filter, or measure. |
| Best use | Reusable business or mathematical routines. | Repeated presentation and analytical transformations across measures. |
Knowledge check
- What logic does SafePercentage centralise?
- What do numerator and denominator represent?
- When is the alternate parameter returned?
- Why does each measure still define its own denominator context?
- How is a UDF different from a Calculation Group?
Troubleshooting
| Problem | What to check |
|---|---|
| FUNCTION is underlined as invalid. | Use Power BI Desktop June 2026 or later and a compatibility-level 1702 model. |
| SafePercentage cannot be found in a measure. | Save the function to the model and confirm it appears under Model explorer → Functions. |
| A percentage shows as a decimal. | Format the calling measure as Percentage. |
| Every category shows 100%. | Ensure the denominator removes DimProduct[Category]. |
| The function is in an error state. | Run INFO.USERDEFINEDFUNCTIONS and inspect ErrorMessage. |
Exercise complete
- SafePercentage runs successfully in DAX Query View.
- The function is saved under Model explorer → Functions.
- Sales and Quantity percentage measures call the function.
- Category results match the control percentages.
- Zero denominators return the selected alternate value.
- The function metadata reports no error.