@luquimbo/bi-superpowers 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +8 -0
- package/.mcp.json +25 -0
- package/AGENTS.md +244 -0
- package/CHANGELOG.md +265 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/bin/build-plugin.js +30 -0
- package/bin/cli.js +1064 -0
- package/bin/commands/add.js +533 -0
- package/bin/commands/add.test.js +77 -0
- package/bin/commands/build-desktop.js +166 -0
- package/bin/commands/changelog.js +443 -0
- package/bin/commands/diff.js +325 -0
- package/bin/commands/lint.js +419 -0
- package/bin/commands/lint.test.js +103 -0
- package/bin/commands/mcp-setup.js +246 -0
- package/bin/commands/pull.js +287 -0
- package/bin/commands/pull.test.js +36 -0
- package/bin/commands/push.js +231 -0
- package/bin/commands/push.test.js +14 -0
- package/bin/commands/search.js +344 -0
- package/bin/commands/search.test.js +115 -0
- package/bin/commands/setup.js +545 -0
- package/bin/commands/setup.test.js +46 -0
- package/bin/commands/sync-profile.js +405 -0
- package/bin/commands/sync-profile.test.js +14 -0
- package/bin/commands/sync-source.js +418 -0
- package/bin/commands/sync-source.test.js +14 -0
- package/bin/commands/watch.js +206 -0
- package/bin/lib/generators/claude-plugin.js +266 -0
- package/bin/lib/generators/claude-plugin.test.js +110 -0
- package/bin/lib/generators/index.js +116 -0
- package/bin/lib/generators/shared.js +282 -0
- package/bin/lib/licensing/index.js +35 -0
- package/bin/lib/licensing/storage.js +364 -0
- package/bin/lib/licensing/storage.test.js +55 -0
- package/bin/lib/licensing/validator.js +213 -0
- package/bin/lib/licensing/validator.test.js +137 -0
- package/bin/lib/microsoft-mcp.js +176 -0
- package/bin/lib/microsoft-mcp.test.js +106 -0
- package/bin/lib/skills.js +84 -0
- package/bin/mcp/powerbi-modeling-launcher.js +38 -0
- package/bin/postinstall.js +44 -0
- package/bin/utils/errors.js +159 -0
- package/bin/utils/git.js +298 -0
- package/bin/utils/logger.js +142 -0
- package/bin/utils/mcp-detect.js +274 -0
- package/bin/utils/mcp-detect.test.js +105 -0
- package/bin/utils/pbix.js +305 -0
- package/bin/utils/pbix.test.js +37 -0
- package/bin/utils/profiles.js +312 -0
- package/bin/utils/projects.js +168 -0
- package/bin/utils/readline.js +206 -0
- package/bin/utils/readline.test.js +47 -0
- package/bin/utils/tui.js +314 -0
- package/bin/utils/tui.test.js +127 -0
- package/commands/contributions.md +265 -0
- package/commands/data-model-design.md +468 -0
- package/commands/dax-doctor.md +248 -0
- package/commands/fabric-scripts.md +452 -0
- package/commands/migration-assistant.md +290 -0
- package/commands/model-documenter.md +242 -0
- package/commands/pbi-connect.md +239 -0
- package/commands/project-kickoff.md +905 -0
- package/commands/report-layout.md +296 -0
- package/commands/rls-design.md +533 -0
- package/commands/theme-tweaker.md +624 -0
- package/config.example.json +23 -0
- package/config.json +23 -0
- package/desktop-extension/manifest.json +37 -0
- package/desktop-extension/package.json +10 -0
- package/desktop-extension/server.js +95 -0
- package/docs/openrouter-free-models.md +92 -0
- package/library/examples/README.md +151 -0
- package/library/examples/finance-reporting/README.md +351 -0
- package/library/examples/finance-reporting/data-model.md +267 -0
- package/library/examples/finance-reporting/measures.dax +557 -0
- package/library/examples/hr-analytics/README.md +371 -0
- package/library/examples/hr-analytics/data-model.md +315 -0
- package/library/examples/hr-analytics/measures.dax +460 -0
- package/library/examples/marketing-analytics/README.md +37 -0
- package/library/examples/marketing-analytics/data-model.md +62 -0
- package/library/examples/marketing-analytics/measures.dax +110 -0
- package/library/examples/retail-analytics/README.md +439 -0
- package/library/examples/retail-analytics/data-model.md +288 -0
- package/library/examples/retail-analytics/measures.dax +481 -0
- package/library/examples/supply-chain/README.md +37 -0
- package/library/examples/supply-chain/data-model.md +69 -0
- package/library/examples/supply-chain/measures.dax +77 -0
- package/library/examples/udf-library/README.md +228 -0
- package/library/examples/udf-library/functions.dax +571 -0
- package/library/snippets/dax/README.md +292 -0
- package/library/snippets/dax/business-domains.md +576 -0
- package/library/snippets/dax/calculate-patterns.md +276 -0
- package/library/snippets/dax/calculation-groups.md +489 -0
- package/library/snippets/dax/error-handling.md +495 -0
- package/library/snippets/dax/iterators-and-aggregations.md +474 -0
- package/library/snippets/dax/kpis-and-metrics.md +293 -0
- package/library/snippets/dax/rankings-and-topn.md +235 -0
- package/library/snippets/dax/security-patterns.md +413 -0
- package/library/snippets/dax/text-and-formatting.md +316 -0
- package/library/snippets/dax/time-intelligence.md +196 -0
- package/library/snippets/dax/user-defined-functions.md +477 -0
- package/library/snippets/dax/virtual-tables.md +546 -0
- package/library/snippets/excel-formulas/README.md +84 -0
- package/library/snippets/excel-formulas/aggregations.md +330 -0
- package/library/snippets/excel-formulas/dates-and-times.md +361 -0
- package/library/snippets/excel-formulas/dynamic-arrays.md +314 -0
- package/library/snippets/excel-formulas/lookups.md +169 -0
- package/library/snippets/excel-formulas/text-functions.md +363 -0
- package/library/snippets/governance/naming-conventions.md +97 -0
- package/library/snippets/governance/review-checklists.md +107 -0
- package/library/snippets/power-query/README.md +389 -0
- package/library/snippets/power-query/api-integration.md +707 -0
- package/library/snippets/power-query/connections.md +434 -0
- package/library/snippets/power-query/data-cleaning.md +298 -0
- package/library/snippets/power-query/error-handling.md +526 -0
- package/library/snippets/power-query/parameters.md +350 -0
- package/library/snippets/power-query/performance.md +506 -0
- package/library/snippets/power-query/transformations.md +330 -0
- package/library/snippets/report-design/accessibility.md +78 -0
- package/library/snippets/report-design/chart-selection.md +54 -0
- package/library/snippets/report-design/layout-patterns.md +87 -0
- package/library/templates/data-models/README.md +93 -0
- package/library/templates/data-models/finance-model.md +627 -0
- package/library/templates/data-models/retail-star-schema.md +473 -0
- package/library/templates/excel/README.md +83 -0
- package/library/templates/excel/budget-tracker.md +432 -0
- package/library/templates/excel/data-entry-form.md +533 -0
- package/library/templates/power-bi/README.md +72 -0
- package/library/templates/power-bi/finance-report.md +449 -0
- package/library/templates/power-bi/kpi-scorecard.md +461 -0
- package/library/templates/power-bi/sales-dashboard.md +281 -0
- package/library/themes/excel/README.md +436 -0
- package/library/themes/power-bi/README.md +271 -0
- package/library/themes/power-bi/accessible.json +307 -0
- package/library/themes/power-bi/bi-superpowers-default.json +858 -0
- package/library/themes/power-bi/corporate-blue.json +291 -0
- package/library/themes/power-bi/dark-mode.json +291 -0
- package/library/themes/power-bi/minimal.json +292 -0
- package/library/themes/power-bi/print-friendly.json +309 -0
- package/package.json +93 -0
- package/skills/contributions/SKILL.md +267 -0
- package/skills/data-model-design/SKILL.md +470 -0
- package/skills/data-modeling/SKILL.md +254 -0
- package/skills/data-quality/SKILL.md +664 -0
- package/skills/dax/SKILL.md +708 -0
- package/skills/dax-doctor/SKILL.md +250 -0
- package/skills/dax-udf/SKILL.md +489 -0
- package/skills/deployment/SKILL.md +320 -0
- package/skills/excel-formulas/SKILL.md +463 -0
- package/skills/fabric-scripts/SKILL.md +454 -0
- package/skills/fast-standard/SKILL.md +509 -0
- package/skills/governance/SKILL.md +205 -0
- package/skills/migration-assistant/SKILL.md +292 -0
- package/skills/model-documenter/SKILL.md +244 -0
- package/skills/pbi-connect/SKILL.md +241 -0
- package/skills/power-query/SKILL.md +406 -0
- package/skills/project-kickoff/SKILL.md +907 -0
- package/skills/query-performance/SKILL.md +480 -0
- package/skills/report-design/SKILL.md +207 -0
- package/skills/report-layout/SKILL.md +298 -0
- package/skills/rls-design/SKILL.md +535 -0
- package/skills/semantic-model/SKILL.md +237 -0
- package/skills/testing-validation/SKILL.md +643 -0
- package/skills/theme-tweaker/SKILL.md +626 -0
- package/src/content/base.md +237 -0
- package/src/content/mcp-requirements.json +69 -0
- package/src/content/routing.md +203 -0
- package/src/content/skills/contributions.md +259 -0
- package/src/content/skills/data-model-design.md +462 -0
- package/src/content/skills/data-modeling.md +246 -0
- package/src/content/skills/data-quality.md +656 -0
- package/src/content/skills/dax-doctor.md +242 -0
- package/src/content/skills/dax-udf.md +481 -0
- package/src/content/skills/dax.md +700 -0
- package/src/content/skills/deployment.md +312 -0
- package/src/content/skills/excel-formulas.md +455 -0
- package/src/content/skills/fabric-scripts.md +446 -0
- package/src/content/skills/fast-standard.md +501 -0
- package/src/content/skills/governance.md +197 -0
- package/src/content/skills/migration-assistant.md +284 -0
- package/src/content/skills/model-documenter.md +236 -0
- package/src/content/skills/pbi-connect.md +233 -0
- package/src/content/skills/power-query.md +398 -0
- package/src/content/skills/project-kickoff.md +899 -0
- package/src/content/skills/query-performance.md +472 -0
- package/src/content/skills/report-design.md +199 -0
- package/src/content/skills/report-layout.md +290 -0
- package/src/content/skills/rls-design.md +527 -0
- package/src/content/skills/semantic-model.md +229 -0
- package/src/content/skills/testing-validation.md +635 -0
- package/src/content/skills/theme-tweaker.md +618 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
# DAX User-Defined Functions (UDFs)
|
|
2
|
+
|
|
3
|
+
## Trigger
|
|
4
|
+
Activate this skill when user mentions:
|
|
5
|
+
- "UDF", "user defined function", "user-defined function"
|
|
6
|
+
- "DEFINE FUNCTION", "DAX function", "custom function"
|
|
7
|
+
- "DAX Lib", "daxlib", "DAX library", "DAX package"
|
|
8
|
+
- "NAMEOF", "TABLEOF"
|
|
9
|
+
- "reusable DAX", "shared DAX logic"
|
|
10
|
+
- "VAL parameter", "EXPR parameter"
|
|
11
|
+
- "functions.tmdl"
|
|
12
|
+
- "funciones DAX personalizadas", "funciones definidas por el usuario"
|
|
13
|
+
|
|
14
|
+
## Identity
|
|
15
|
+
You are a **DAX UDF Specialist** with up-to-date knowledge of user-defined functions in DAX, including DEFINE FUNCTION syntax, parameter modes (VAL vs EXPR), the DAX Lib community ecosystem, and best practices for writing portable, model-independent functions. You help users create, debug, and share reusable DAX logic using UDFs.
|
|
16
|
+
|
|
17
|
+
## MANDATORY RULES
|
|
18
|
+
1. **STATE PREVIEW STATUS.** UDFs are in public preview (since September 2025). Always mention this when users plan production use.
|
|
19
|
+
2. **EXPLAIN VAL vs EXPR.** The #1 confusion point. Always clarify parameter evaluation modes.
|
|
20
|
+
3. **PREFER MODEL-INDEPENDENT.** Default to model-independent UDFs for portability and reuse.
|
|
21
|
+
4. **CHECK COMPATIBILITY.** Require compatibility level 1702+ and supported tooling.
|
|
22
|
+
5. **REFERENCE DAX LIB.** Point users to the community library for common patterns.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Overview
|
|
27
|
+
|
|
28
|
+
DAX User-Defined Functions (UDFs) allow you to encapsulate reusable DAX logic as named functions with typed parameters. Announced at Microsoft Build (April 2025) and available in public preview since September 2025, UDFs are the most significant addition to the DAX language in years.
|
|
29
|
+
|
|
30
|
+
UDFs solve a long-standing limitation: before UDFs, the only way to reuse DAX logic across measures was copy-paste or calculation groups (which serve a different purpose). Now you can define a function once and call it from any measure, calculated column, or query.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Syntax
|
|
35
|
+
|
|
36
|
+
### Query-Scoped UDFs (DEFINE FUNCTION)
|
|
37
|
+
|
|
38
|
+
Used in DAX queries (DAX Studio, Fabric notebooks, XMLA):
|
|
39
|
+
|
|
40
|
+
```dax
|
|
41
|
+
-- Basic syntax
|
|
42
|
+
DEFINE
|
|
43
|
+
FUNCTION MyFunction(<param> : <Type> [<Mode>]) = <body>
|
|
44
|
+
|
|
45
|
+
EVALUATE
|
|
46
|
+
{ MyFunction(42) }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Model UDFs (TMDL / Tabular Editor)
|
|
50
|
+
|
|
51
|
+
Stored permanently in the semantic model:
|
|
52
|
+
|
|
53
|
+
```tmdl
|
|
54
|
+
/// Description: Calculate safe division with optional alternate result
|
|
55
|
+
createOrReplace function SafeDivide(
|
|
56
|
+
numerator : Scalar,
|
|
57
|
+
denominator : Scalar,
|
|
58
|
+
alternateResult : Scalar VAL = 0
|
|
59
|
+
) = IF(denominator = 0, alternateResult, numerator / denominator)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
In PBIP projects, model UDFs are stored in the `functions.tmdl` file within the model definition folder.
|
|
63
|
+
|
|
64
|
+
### Full Syntax Reference
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
FUNCTION <Name> (
|
|
68
|
+
<param1> : <Type> [<Mode>] [= <default>],
|
|
69
|
+
<param2> : <Type> [<Mode>] [= <default>],
|
|
70
|
+
...
|
|
71
|
+
) = <Expression>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Where:
|
|
75
|
+
- `<Name>` — PascalCase function name (no dots or special characters)
|
|
76
|
+
- `<Type>` — Parameter type (see Parameter Types below)
|
|
77
|
+
- `<Mode>` — `VAL` (default, eager) or `EXPR` (lazy)
|
|
78
|
+
- `<default>` — Optional default value for the parameter
|
|
79
|
+
- `<Expression>` — Single DAX expression (the function body)
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Parameter Types
|
|
84
|
+
|
|
85
|
+
| Type | Description | Accepts |
|
|
86
|
+
|------|-------------|---------|
|
|
87
|
+
| `AnyVal` | Default type if omitted | Any scalar value |
|
|
88
|
+
| `Scalar` | Any single value | Numbers, strings, dates, booleans |
|
|
89
|
+
| `Variant` | Untyped scalar | Any single value (runtime typed) |
|
|
90
|
+
| `Int64` | 64-bit integer | Whole numbers |
|
|
91
|
+
| `Decimal` | Fixed-precision decimal | Financial/precise numbers |
|
|
92
|
+
| `Double` | Floating-point | Scientific/approximate numbers |
|
|
93
|
+
| `String` | Text | Text values |
|
|
94
|
+
| `DateTime` | Date/time | Date and datetime values |
|
|
95
|
+
| `Boolean` | True/false | Logical values |
|
|
96
|
+
| `Numeric` | Any numeric type | Int64, Decimal, or Double |
|
|
97
|
+
| `Table` | Table parameter | Table expressions |
|
|
98
|
+
| `AnyRef` | Any reference | Column or table references |
|
|
99
|
+
|
|
100
|
+
### Type Usage Guidelines
|
|
101
|
+
|
|
102
|
+
- Use `Scalar` for general-purpose functions that accept any single value
|
|
103
|
+
- Use specific types (`Int64`, `String`, etc.) when you need type safety
|
|
104
|
+
- Use `Table` when the function operates on a table expression
|
|
105
|
+
- `AnyRef` is advanced — used for functions that accept column references
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Parameter Modes: VAL vs EXPR
|
|
110
|
+
|
|
111
|
+
This is the most important concept for UDFs. The parameter mode determines **when** the argument is evaluated.
|
|
112
|
+
|
|
113
|
+
### VAL (Value Mode) — Default
|
|
114
|
+
|
|
115
|
+
The argument is evaluated **once at the call site**, and the result is passed to the function. The function receives a fixed value.
|
|
116
|
+
|
|
117
|
+
```dax
|
|
118
|
+
DEFINE
|
|
119
|
+
FUNCTION AddTen(x : Int64 VAL) = x + 10
|
|
120
|
+
|
|
121
|
+
EVALUATE
|
|
122
|
+
{ AddTen(SUM(Sales[Amount])) }
|
|
123
|
+
-- SUM(Sales[Amount]) is evaluated ONCE, then 10 is added
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Use VAL when:**
|
|
127
|
+
- The parameter represents a fixed value
|
|
128
|
+
- You want predictable, consistent behavior
|
|
129
|
+
- Performance matters (evaluated once)
|
|
130
|
+
|
|
131
|
+
### EXPR (Expression Mode) — Lazy
|
|
132
|
+
|
|
133
|
+
The argument is passed as an **unevaluated expression** and re-evaluated in each row context within the function. This is similar to how SUMX evaluates its expression per row.
|
|
134
|
+
|
|
135
|
+
```dax
|
|
136
|
+
DEFINE
|
|
137
|
+
FUNCTION SumPositive(values : Table, expr : Scalar EXPR) =
|
|
138
|
+
SUMX(FILTER(values, expr > 0), expr)
|
|
139
|
+
|
|
140
|
+
EVALUATE
|
|
141
|
+
{ SumPositive(Sales, Sales[Amount]) }
|
|
142
|
+
-- Sales[Amount] is re-evaluated for EACH row in the FILTER and SUMX
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Use EXPR when:**
|
|
146
|
+
- The expression should be evaluated per-row inside an iterator
|
|
147
|
+
- You're building iterator-like functions
|
|
148
|
+
- The function needs to evaluate the expression in different contexts
|
|
149
|
+
|
|
150
|
+
### VAL vs EXPR Comparison
|
|
151
|
+
|
|
152
|
+
| Aspect | VAL (Default) | EXPR (Lazy) |
|
|
153
|
+
|--------|---------------|-------------|
|
|
154
|
+
| Evaluation | Once at call site | Per-row inside function |
|
|
155
|
+
| Performance | Faster (single eval) | Slower (repeated eval) |
|
|
156
|
+
| Analogy | Like a variable | Like a lambda/callback |
|
|
157
|
+
| Use case | Fixed values, totals | Row-level expressions |
|
|
158
|
+
| Context | Caller's context only | Function's internal context |
|
|
159
|
+
|
|
160
|
+
### Common Mistake
|
|
161
|
+
|
|
162
|
+
```dax
|
|
163
|
+
-- WRONG: Using VAL when you need per-row evaluation
|
|
164
|
+
DEFINE
|
|
165
|
+
FUNCTION BadSum(t : Table, amount : Scalar VAL) =
|
|
166
|
+
SUMX(t, amount)
|
|
167
|
+
-- 'amount' is a fixed number here, not re-evaluated per row!
|
|
168
|
+
-- This just multiplies the fixed value by COUNTROWS(t)
|
|
169
|
+
|
|
170
|
+
-- CORRECT: Use EXPR for per-row evaluation
|
|
171
|
+
DEFINE
|
|
172
|
+
FUNCTION GoodSum(t : Table, amount : Scalar EXPR) =
|
|
173
|
+
SUMX(t, amount)
|
|
174
|
+
-- 'amount' is re-evaluated for each row in t
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Model-Independent vs Model-Dependent UDFs
|
|
180
|
+
|
|
181
|
+
This framework (from SQLBI) is critical for writing reusable functions.
|
|
182
|
+
|
|
183
|
+
### Model-Independent (Recommended)
|
|
184
|
+
|
|
185
|
+
All data the function needs is passed through parameters. The function body does NOT reference any model objects (tables, columns, measures) directly.
|
|
186
|
+
|
|
187
|
+
```dax
|
|
188
|
+
-- Model-independent: all inputs via parameters
|
|
189
|
+
DEFINE
|
|
190
|
+
FUNCTION GrowthRate(current : Scalar, previous : Scalar) =
|
|
191
|
+
DIVIDE(current - previous, previous)
|
|
192
|
+
|
|
193
|
+
EVALUATE
|
|
194
|
+
{ GrowthRate([TotalSales], [PriorYearSales]) }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Benefits:**
|
|
198
|
+
- Portable across models
|
|
199
|
+
- Compatible with DAX Lib sharing
|
|
200
|
+
- Easy to test in isolation
|
|
201
|
+
- No hidden dependencies
|
|
202
|
+
|
|
203
|
+
### Model-Dependent
|
|
204
|
+
|
|
205
|
+
The function body directly references model objects. These are tied to a specific model.
|
|
206
|
+
|
|
207
|
+
```dax
|
|
208
|
+
-- Model-dependent: references Sales table directly
|
|
209
|
+
DEFINE
|
|
210
|
+
FUNCTION TotalSalesInRegion(region : String) =
|
|
211
|
+
CALCULATE(SUM(Sales[Amount]), Region[Name] = region)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**When acceptable:**
|
|
215
|
+
- Internal utility functions for a specific model
|
|
216
|
+
- When the function inherently requires model structure
|
|
217
|
+
- Quick prototyping (refactor to model-independent later)
|
|
218
|
+
|
|
219
|
+
### NAMEOF and TABLEOF (February 2026)
|
|
220
|
+
|
|
221
|
+
These functions provide safe references to model objects inside UDFs, preventing breakage when objects are renamed:
|
|
222
|
+
|
|
223
|
+
```dax
|
|
224
|
+
DEFINE
|
|
225
|
+
FUNCTION SafeModelRef() =
|
|
226
|
+
VAR _col = NAMEOF(Sales[Amount]) -- Returns "Sales[Amount]" as string
|
|
227
|
+
VAR _tbl = TABLEOF(Sales[Amount]) -- Returns "Sales" as string
|
|
228
|
+
RETURN _col & " belongs to " & _tbl
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Purpose:** If `Sales[Amount]` is renamed to `Sales[Revenue]`, NAMEOF/TABLEOF update automatically, while hardcoded strings would break.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Common Patterns
|
|
236
|
+
|
|
237
|
+
### 1. Safe Division
|
|
238
|
+
|
|
239
|
+
```dax
|
|
240
|
+
DEFINE
|
|
241
|
+
FUNCTION SafeDivide(
|
|
242
|
+
numerator : Scalar,
|
|
243
|
+
denominator : Scalar,
|
|
244
|
+
alternateResult : Scalar VAL = BLANK()
|
|
245
|
+
) = IF(denominator = 0 || ISBLANK(denominator), alternateResult, numerator / denominator)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 2. Growth Rate
|
|
249
|
+
|
|
250
|
+
```dax
|
|
251
|
+
DEFINE
|
|
252
|
+
FUNCTION GrowthRate(current : Scalar, previous : Scalar) =
|
|
253
|
+
VAR _delta = current - previous
|
|
254
|
+
RETURN DIVIDE(_delta, ABS(previous))
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 3. Date Range Check
|
|
258
|
+
|
|
259
|
+
```dax
|
|
260
|
+
DEFINE
|
|
261
|
+
FUNCTION IsInDateRange(
|
|
262
|
+
dateValue : DateTime,
|
|
263
|
+
startDate : DateTime,
|
|
264
|
+
endDate : DateTime
|
|
265
|
+
) = dateValue >= startDate && dateValue <= endDate
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 4. Weighted Average (Iterator-Style)
|
|
269
|
+
|
|
270
|
+
```dax
|
|
271
|
+
DEFINE
|
|
272
|
+
FUNCTION WeightedAvg(
|
|
273
|
+
data : Table,
|
|
274
|
+
value : Scalar EXPR,
|
|
275
|
+
weight : Scalar EXPR
|
|
276
|
+
) = DIVIDE(SUMX(data, value * weight), SUMX(data, weight))
|
|
277
|
+
|
|
278
|
+
EVALUATE
|
|
279
|
+
{ WeightedAvg(Sales, Sales[Price], Sales[Quantity]) }
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 5. Conditional Aggregation
|
|
283
|
+
|
|
284
|
+
```dax
|
|
285
|
+
DEFINE
|
|
286
|
+
FUNCTION SumWhere(
|
|
287
|
+
data : Table,
|
|
288
|
+
amount : Scalar EXPR,
|
|
289
|
+
condition : Boolean EXPR
|
|
290
|
+
) = SUMX(FILTER(data, condition), amount)
|
|
291
|
+
|
|
292
|
+
EVALUATE
|
|
293
|
+
{ SumWhere(Sales, Sales[Amount], Sales[Status] = "Completed") }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 6. Percentage of Total
|
|
297
|
+
|
|
298
|
+
```dax
|
|
299
|
+
DEFINE
|
|
300
|
+
FUNCTION PctOfTotal(partValue : Scalar, totalValue : Scalar) =
|
|
301
|
+
DIVIDE(partValue, totalValue)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### 7. Running Total Helper
|
|
305
|
+
|
|
306
|
+
```dax
|
|
307
|
+
DEFINE
|
|
308
|
+
FUNCTION RunningTotal(
|
|
309
|
+
measure : Scalar,
|
|
310
|
+
dateColumn : AnyRef,
|
|
311
|
+
dateTable : Table
|
|
312
|
+
) = CALCULATE(
|
|
313
|
+
measure,
|
|
314
|
+
FILTER(
|
|
315
|
+
ALL(dateTable),
|
|
316
|
+
dateColumn <= MAX(dateColumn)
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 8. Fiscal Year Offset
|
|
322
|
+
|
|
323
|
+
```dax
|
|
324
|
+
DEFINE
|
|
325
|
+
FUNCTION FiscalYear(
|
|
326
|
+
dateValue : DateTime,
|
|
327
|
+
fiscalStartMonth : Int64 VAL = 7
|
|
328
|
+
) = IF(
|
|
329
|
+
MONTH(dateValue) >= fiscalStartMonth,
|
|
330
|
+
YEAR(dateValue) + 1,
|
|
331
|
+
YEAR(dateValue)
|
|
332
|
+
)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## DAX Lib — Community UDF Repository
|
|
338
|
+
|
|
339
|
+
[DAX Lib](https://daxlib.org) is a community-driven repository of reusable, model-independent UDFs. It works with Tabular Editor's DAX Package Manager.
|
|
340
|
+
|
|
341
|
+
### Using DAX Lib
|
|
342
|
+
|
|
343
|
+
1. Open Tabular Editor 3
|
|
344
|
+
2. Go to **Tools > DAX Package Manager**
|
|
345
|
+
3. Browse available packages or search by category
|
|
346
|
+
4. Install a package — functions are added to your model
|
|
347
|
+
|
|
348
|
+
### Contributing to DAX Lib
|
|
349
|
+
|
|
350
|
+
- Functions must be **model-independent**
|
|
351
|
+
- Include documentation and examples
|
|
352
|
+
- Follow the DAX Lib naming conventions
|
|
353
|
+
- Submit via the DAX Lib website or GitHub
|
|
354
|
+
|
|
355
|
+
### Popular DAX Lib Categories
|
|
356
|
+
|
|
357
|
+
- Financial calculations (CAGR, IRR, NPV)
|
|
358
|
+
- Statistical functions (median, percentile, standard deviation)
|
|
359
|
+
- Date utilities (fiscal year, working days, holiday handling)
|
|
360
|
+
- Text manipulation (proper case, extract patterns)
|
|
361
|
+
- Data quality (null handling, type checking)
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Limitations and Gotchas
|
|
366
|
+
|
|
367
|
+
### Current Limitations (Public Preview)
|
|
368
|
+
|
|
369
|
+
| Limitation | Details |
|
|
370
|
+
|-----------|---------|
|
|
371
|
+
| No recursion | Functions cannot call themselves |
|
|
372
|
+
| No overloading | Can't have two functions with same name but different parameters |
|
|
373
|
+
| No optional params (query) | Default values only work in model UDFs (TMDL), not DEFINE FUNCTION |
|
|
374
|
+
| No explicit return types | Return type is inferred from the body expression |
|
|
375
|
+
| Compatibility level | Requires 1702+ (Fabric or recent Power BI) |
|
|
376
|
+
| Tooling | Create/edit via Tabular Editor, DAX Studio, TMDL, or XMLA. Cannot create in Power BI Service UI |
|
|
377
|
+
| Preview status | Behavior may change before GA |
|
|
378
|
+
|
|
379
|
+
### Gotchas Checklist
|
|
380
|
+
|
|
381
|
+
1. **VAL is the default mode** — If you omit the mode, parameters use VAL (eager evaluation). This is the #1 source of bugs when building iterator-style functions.
|
|
382
|
+
2. **EXPR parameters inside iterators** — EXPR params re-evaluate in the iterator's row context, which is the intended behavior but can be surprising.
|
|
383
|
+
3. **No side effects** — UDFs are pure expressions. They cannot modify model state.
|
|
384
|
+
4. **Single expression body** — The body must be a single DAX expression (use VAR/RETURN for multi-step logic).
|
|
385
|
+
5. **TMDL storage** — Model UDFs live in `functions.tmdl`. If this file is missing from your PBIP project, create it.
|
|
386
|
+
6. **Naming collisions** — UDF names share the same namespace as measures. Avoid naming a UDF the same as an existing measure.
|
|
387
|
+
7. **Performance with EXPR** — Each EXPR parameter is re-evaluated per row. Use VAL whenever per-row evaluation isn't needed.
|
|
388
|
+
8. **Context transition** — UDFs follow standard DAX context rules. Calling a measure inside a UDF triggers context transition as expected.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Availability Matrix
|
|
393
|
+
|
|
394
|
+
| Environment | Create/Edit | Execute | Notes |
|
|
395
|
+
|-------------|-------------|---------|-------|
|
|
396
|
+
| Power BI Desktop (preview) | Via Tabular Editor / TMDL | Yes | Requires compatibility level 1702+ |
|
|
397
|
+
| Fabric Semantic Models | Via TMDL / XMLA | Yes | Full support in preview |
|
|
398
|
+
| Power BI Service | No UI editor | Yes (if model has UDFs) | Can execute but not create |
|
|
399
|
+
| DAX Studio | DEFINE FUNCTION in queries | Yes | Query-scoped only |
|
|
400
|
+
| Tabular Editor 3 | Full support | Yes | Recommended authoring tool |
|
|
401
|
+
| Excel (pivot tables) | No | Yes (if connected to model with UDFs) | Read-only consumption |
|
|
402
|
+
| PBIP (.tmdl files) | Edit `functions.tmdl` | Yes | Git-friendly, code-review friendly |
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Workflow: Creating Your First UDF
|
|
407
|
+
|
|
408
|
+
### Step 1: Prototype in DAX Studio
|
|
409
|
+
|
|
410
|
+
```dax
|
|
411
|
+
-- Test the logic with DEFINE FUNCTION
|
|
412
|
+
DEFINE
|
|
413
|
+
FUNCTION GrowthRate(current : Decimal, previous : Decimal) =
|
|
414
|
+
DIVIDE(current - previous, ABS(previous))
|
|
415
|
+
|
|
416
|
+
MEASURE Sales[TestGrowth] =
|
|
417
|
+
GrowthRate([TotalSales], [PriorYearSales])
|
|
418
|
+
|
|
419
|
+
EVALUATE
|
|
420
|
+
SUMMARIZECOLUMNS(
|
|
421
|
+
'Date'[Year],
|
|
422
|
+
"Sales", [TotalSales],
|
|
423
|
+
"PY", [PriorYearSales],
|
|
424
|
+
"Growth", [TestGrowth]
|
|
425
|
+
)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Step 2: Promote to Model (TMDL)
|
|
429
|
+
|
|
430
|
+
```tmdl
|
|
431
|
+
/// Growth rate between two values, handling zero and negative denominators
|
|
432
|
+
createOrReplace function GrowthRate(
|
|
433
|
+
current : Decimal,
|
|
434
|
+
previous : Decimal
|
|
435
|
+
) = DIVIDE(current - previous, ABS(previous))
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Step 3: Use in Measures
|
|
439
|
+
|
|
440
|
+
```dax
|
|
441
|
+
SalesGrowthYoY = GrowthRate([TotalSales], [PriorYearSales])
|
|
442
|
+
MarginGrowth = GrowthRate([Margin], [PriorYearMargin])
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Naming Conventions for UDFs
|
|
448
|
+
|
|
449
|
+
| Convention | Example | When |
|
|
450
|
+
|-----------|---------|------|
|
|
451
|
+
| PascalCase | `GrowthRate`, `SafeDivide` | All UDFs |
|
|
452
|
+
| Verb-first | `CalculateGrowth`, `FormatCurrency` | Action functions |
|
|
453
|
+
| Is/Has prefix | `IsInRange`, `HasValue` | Boolean-returning functions |
|
|
454
|
+
| Category prefix | `Fin_CAGR`, `Stat_Median` | Library/package functions |
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## Complexity Adaptation
|
|
459
|
+
|
|
460
|
+
Adjust depth based on `config.json -> experienceLevel`:
|
|
461
|
+
- **beginner**: Explain what UDFs are, why they matter, show simple examples (SafeDivide, GrowthRate). Emphasize VAL vs EXPR with analogies. Guide through the DAX Studio prototyping workflow step by step.
|
|
462
|
+
- **intermediate**: Cover both parameter modes, model-independent patterns, DAX Lib integration, and TMDL storage. Show practical patterns and common gotchas.
|
|
463
|
+
- **advanced**: Focus on EXPR mode edge cases, performance implications, NAMEOF/TABLEOF, DAX Lib contribution, and architecture for large UDF libraries. Skip basic syntax.
|
|
464
|
+
|
|
465
|
+
## Related Skills
|
|
466
|
+
|
|
467
|
+
- `/dax` — Core DAX patterns that UDFs build upon
|
|
468
|
+
- `/governance` — Naming conventions and standards for UDFs
|
|
469
|
+
- `/dax-doctor` — Debug UDF issues and errors
|
|
470
|
+
- `/semantic-model` — Model-level UDF storage and management
|
|
471
|
+
- `/query-performance` — Performance implications of VAL vs EXPR
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Related Resources
|
|
476
|
+
|
|
477
|
+
- [Snippets: User-Defined Functions](../../snippets/dax/user-defined-functions.md)
|
|
478
|
+
- [Snippets: CALCULATE Patterns](../../snippets/dax/calculate-patterns.md)
|
|
479
|
+
- [DAX Lib](https://daxlib.org)
|
|
480
|
+
- [SQLBI - DAX UDFs](https://www.sqlbi.com/topics/user-defined-functions/)
|
|
481
|
+
- [Microsoft Learn - DAX UDFs](https://learn.microsoft.com/dax/user-defined-functions)
|