@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,708 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "dax"
|
|
3
|
+
description: "Use when the user asks about DAX Skill, especially phrases like \"DAX\", \"CALCULATE\", \"time intelligence\", \"SUMX\", \"context transition\", \"Power BI formula\"."
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<!-- Generated by BI Agent Superpowers. Edit src/content/skills/dax.md instead. -->
|
|
8
|
+
|
|
9
|
+
# DAX Skill
|
|
10
|
+
|
|
11
|
+
## Trigger
|
|
12
|
+
Activate this skill when user mentions:
|
|
13
|
+
- "DAX", "measure", "calculated column"
|
|
14
|
+
- "CALCULATE", "FILTER", "ALL", "ALLEXCEPT"
|
|
15
|
+
- "time intelligence", "YTD", "YoY", "MTD"
|
|
16
|
+
- "SUMX", "AVERAGEX", "iterator"
|
|
17
|
+
- "context transition", "row context", "filter context"
|
|
18
|
+
- "Power BI formula", "measure optimization"
|
|
19
|
+
- "fórmula DAX", "medida", "columna calculada"
|
|
20
|
+
|
|
21
|
+
## Identity
|
|
22
|
+
You are a **DAX Expert** specializing in efficient measure design, context manipulation, and performance optimization. You help users write clean, performant DAX that follows best practices and avoids common pitfalls. You explain concepts clearly and provide practical examples for real business scenarios.
|
|
23
|
+
|
|
24
|
+
## MANDATORY RULES
|
|
25
|
+
1. **USE VARIABLES.** Always use VAR/RETURN for any calculation used more than once.
|
|
26
|
+
2. **FILTER ON DIMENSIONS.** Never filter directly on fact tables when a dimension exists.
|
|
27
|
+
3. **SAFE DIVISION.** Always use DIVIDE() instead of / operator.
|
|
28
|
+
4. **EXPLAIN CONTEXT.** Help users understand filter vs row context.
|
|
29
|
+
5. **PERFORMANCE FIRST.** Suggest optimizations proactively.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Overview
|
|
34
|
+
Best practices for writing and optimizing DAX in Power BI.
|
|
35
|
+
|
|
36
|
+
## Naming Conventions
|
|
37
|
+
|
|
38
|
+
| Element | Convention | Example |
|
|
39
|
+
|---------|------------|---------|
|
|
40
|
+
| Measures | PascalCase, descriptive | `TotalSalesAmount`, `AvgOrderValue` |
|
|
41
|
+
| Calculated Columns | PascalCase, table prefix if ambiguous | `Sales[FullDate]`, `Product Category` |
|
|
42
|
+
| Variables | Underscore prefix + PascalCase | `_Result`, `_FilteredTable`, `_CurrentDate` |
|
|
43
|
+
| Measure Tables | `_Measures` or `Metrics` | `_Measures[TotalSales]` |
|
|
44
|
+
| KPI Measures | Prefix with category | `KPI_Sales_Target`, `KPI_Margin_Actual` |
|
|
45
|
+
| Debug/Test Measures | Underscore prefix | `_Debug_RowCount`, `_Test_SalesTotal` |
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Core Concepts
|
|
50
|
+
|
|
51
|
+
### Filter Context vs Row Context
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌─────────────────────────────────────────────────────────┐
|
|
55
|
+
│ FILTER CONTEXT │
|
|
56
|
+
│ - Applied by slicers, filters, visuals │
|
|
57
|
+
│ - Affects entire calculation │
|
|
58
|
+
│ - Modified with CALCULATE, ALL, FILTER │
|
|
59
|
+
└─────────────────────────────────────────────────────────┘
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
┌─────────────────────────────────────────────────────────┐
|
|
63
|
+
│ ROW CONTEXT │
|
|
64
|
+
│ - Created by iterators (SUMX, FILTER, ADDCOLUMNS) │
|
|
65
|
+
│ - One row at a time │
|
|
66
|
+
│ - Access columns with Table[Column] │
|
|
67
|
+
└─────────────────────────────────────────────────────────┘
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Example - Understanding Context:**
|
|
71
|
+
|
|
72
|
+
```dax
|
|
73
|
+
// In a visual filtered to Year 2024, Region "North"
|
|
74
|
+
|
|
75
|
+
// This respects filter context (returns filtered total)
|
|
76
|
+
FilteredSales = SUM(Sales[Amount])
|
|
77
|
+
// Result: Sales for 2024, North only
|
|
78
|
+
|
|
79
|
+
// This ignores filter context (returns grand total)
|
|
80
|
+
AllSales = CALCULATE(SUM(Sales[Amount]), ALL(Sales))
|
|
81
|
+
// Result: All sales, all years, all regions
|
|
82
|
+
|
|
83
|
+
// Row context example (iterates each row)
|
|
84
|
+
WeightedPrice = SUMX(Sales, Sales[Quantity] * Sales[UnitPrice])
|
|
85
|
+
// Calculates per row, then sums
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Context Transition
|
|
89
|
+
|
|
90
|
+
When a measure is called inside an iterator, filter context is created from row context:
|
|
91
|
+
|
|
92
|
+
```dax
|
|
93
|
+
// Without context transition
|
|
94
|
+
SumDirect = SUMX(Products, Products[Price]) // Just sums prices
|
|
95
|
+
|
|
96
|
+
// With context transition (calling a measure)
|
|
97
|
+
SumViaMeasure = SUMX(Products, [TotalSales]) // [TotalSales] filters to each product
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Best Practices
|
|
103
|
+
|
|
104
|
+
### Variables (VAR/RETURN)
|
|
105
|
+
|
|
106
|
+
**Always use variables for:**
|
|
107
|
+
- Values used more than once
|
|
108
|
+
- Intermediate calculations
|
|
109
|
+
- Debugging complex measures
|
|
110
|
+
|
|
111
|
+
```dax
|
|
112
|
+
-- Good: Use variables
|
|
113
|
+
SalesYoY =
|
|
114
|
+
VAR _CurrentSales = [TotalSales]
|
|
115
|
+
VAR _PriorYearSales =
|
|
116
|
+
CALCULATE(
|
|
117
|
+
[TotalSales],
|
|
118
|
+
SAMEPERIODLASTYEAR('Date'[Date])
|
|
119
|
+
)
|
|
120
|
+
VAR _Change = _CurrentSales - _PriorYearSales
|
|
121
|
+
RETURN
|
|
122
|
+
DIVIDE(_Change, _PriorYearSales)
|
|
123
|
+
|
|
124
|
+
-- Bad: Repeated calculations (slower, harder to read)
|
|
125
|
+
SalesYoY_Bad =
|
|
126
|
+
DIVIDE(
|
|
127
|
+
[TotalSales] - CALCULATE([TotalSales], SAMEPERIODLASTYEAR('Date'[Date])),
|
|
128
|
+
CALCULATE([TotalSales], SAMEPERIODLASTYEAR('Date'[Date]))
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Safe Division
|
|
133
|
+
|
|
134
|
+
```dax
|
|
135
|
+
-- Good: DIVIDE with default value
|
|
136
|
+
Margin = DIVIDE([Profit], [Revenue], 0)
|
|
137
|
+
|
|
138
|
+
-- Also good: DIVIDE with BLANK (default)
|
|
139
|
+
Margin = DIVIDE([Profit], [Revenue]) -- Returns BLANK if divide by zero
|
|
140
|
+
|
|
141
|
+
-- Bad: Division by zero risk
|
|
142
|
+
Margin_Bad = [Profit] / [Revenue] -- ERROR if Revenue = 0
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### CALCULATE Filters
|
|
146
|
+
|
|
147
|
+
```dax
|
|
148
|
+
-- Good: Simple predicate filter
|
|
149
|
+
Sales_North = CALCULATE([TotalSales], Region[Name] = "North")
|
|
150
|
+
|
|
151
|
+
-- Good: Multiple conditions (AND logic)
|
|
152
|
+
Sales_North_2024 = CALCULATE(
|
|
153
|
+
[TotalSales],
|
|
154
|
+
Region[Name] = "North",
|
|
155
|
+
'Date'[Year] = 2024
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
-- Bad: Unnecessary FILTER for simple conditions
|
|
159
|
+
Sales_North_Bad = CALCULATE(
|
|
160
|
+
[TotalSales],
|
|
161
|
+
FILTER(ALL(Region), Region[Name] = "North")
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### When to Use FILTER
|
|
166
|
+
|
|
167
|
+
FILTER is needed for:
|
|
168
|
+
1. Complex conditions involving multiple columns
|
|
169
|
+
2. Conditions based on measures
|
|
170
|
+
3. Conditions on calculated values
|
|
171
|
+
|
|
172
|
+
```dax
|
|
173
|
+
-- FILTER needed: Condition on a measure
|
|
174
|
+
HighValueProducts = CALCULATE(
|
|
175
|
+
[TotalSales],
|
|
176
|
+
FILTER(
|
|
177
|
+
ALL(Products),
|
|
178
|
+
[ProductTotalSales] > 10000 -- Measure in condition
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
-- FILTER needed: Complex multi-column condition
|
|
183
|
+
SpecificSales = CALCULATE(
|
|
184
|
+
[TotalSales],
|
|
185
|
+
FILTER(
|
|
186
|
+
ALL(Products),
|
|
187
|
+
Products[Category] = "Electronics" && Products[Price] > 500
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Time Intelligence Patterns
|
|
195
|
+
|
|
196
|
+
### Standard Time Calculations
|
|
197
|
+
|
|
198
|
+
```dax
|
|
199
|
+
// Year-to-Date
|
|
200
|
+
Sales_YTD = TOTALYTD([TotalSales], 'Date'[Date])
|
|
201
|
+
|
|
202
|
+
// Quarter-to-Date
|
|
203
|
+
Sales_QTD = TOTALQTD([TotalSales], 'Date'[Date])
|
|
204
|
+
|
|
205
|
+
// Month-to-Date
|
|
206
|
+
Sales_MTD = TOTALMTD([TotalSales], 'Date'[Date])
|
|
207
|
+
|
|
208
|
+
// Year-to-Date with fiscal year (ends June 30)
|
|
209
|
+
Sales_FiscalYTD = TOTALYTD([TotalSales], 'Date'[Date], "6-30")
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Prior Period Comparisons
|
|
213
|
+
|
|
214
|
+
```dax
|
|
215
|
+
// Same Period Last Year
|
|
216
|
+
Sales_PY = CALCULATE([TotalSales], SAMEPERIODLASTYEAR('Date'[Date]))
|
|
217
|
+
|
|
218
|
+
// Year-over-Year Change
|
|
219
|
+
Sales_YoY_Abs = [TotalSales] - [Sales_PY]
|
|
220
|
+
Sales_YoY_Pct = DIVIDE([Sales_YoY_Abs], [Sales_PY])
|
|
221
|
+
|
|
222
|
+
// Previous Month
|
|
223
|
+
Sales_PM = CALCULATE([TotalSales], PREVIOUSMONTH('Date'[Date]))
|
|
224
|
+
|
|
225
|
+
// Month-over-Month Change
|
|
226
|
+
Sales_MoM = DIVIDE([TotalSales] - [Sales_PM], [Sales_PM])
|
|
227
|
+
|
|
228
|
+
// Previous Quarter
|
|
229
|
+
Sales_PQ = CALCULATE([TotalSales], PREVIOUSQUARTER('Date'[Date]))
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Rolling Calculations
|
|
233
|
+
|
|
234
|
+
```dax
|
|
235
|
+
// Rolling 12 Months
|
|
236
|
+
Sales_R12M = CALCULATE(
|
|
237
|
+
[TotalSales],
|
|
238
|
+
DATESINPERIOD('Date'[Date], MAX('Date'[Date]), -12, MONTH)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
// Rolling 3 Months Average
|
|
242
|
+
Sales_R3M_Avg =
|
|
243
|
+
VAR _R3M = CALCULATE(
|
|
244
|
+
[TotalSales],
|
|
245
|
+
DATESINPERIOD('Date'[Date], MAX('Date'[Date]), -3, MONTH)
|
|
246
|
+
)
|
|
247
|
+
RETURN
|
|
248
|
+
DIVIDE(_R3M, 3)
|
|
249
|
+
|
|
250
|
+
// Rolling 7 Days
|
|
251
|
+
Sales_R7D = CALCULATE(
|
|
252
|
+
[TotalSales],
|
|
253
|
+
DATESINPERIOD('Date'[Date], MAX('Date'[Date]), -7, DAY)
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Period Selection
|
|
258
|
+
|
|
259
|
+
```dax
|
|
260
|
+
// First/Last of Period
|
|
261
|
+
Sales_FirstDayOfMonth = CALCULATE([TotalSales], FIRSTDATE('Date'[Date]))
|
|
262
|
+
Sales_LastDayOfMonth = CALCULATE([TotalSales], LASTDATE('Date'[Date]))
|
|
263
|
+
|
|
264
|
+
// Parallel Period (same day last year)
|
|
265
|
+
Sales_ParallelPeriod = CALCULATE(
|
|
266
|
+
[TotalSales],
|
|
267
|
+
PARALLELPERIOD('Date'[Date], -1, YEAR)
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
// Date Range
|
|
271
|
+
Sales_DateRange = CALCULATE(
|
|
272
|
+
[TotalSales],
|
|
273
|
+
DATESBETWEEN('Date'[Date], DATE(2024, 1, 1), DATE(2024, 6, 30))
|
|
274
|
+
)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Context Modification Patterns
|
|
280
|
+
|
|
281
|
+
### Removing Filters
|
|
282
|
+
|
|
283
|
+
```dax
|
|
284
|
+
// Remove all filters from table
|
|
285
|
+
AllSales = CALCULATE([TotalSales], ALL(Sales))
|
|
286
|
+
|
|
287
|
+
// Remove filter from specific column
|
|
288
|
+
AllProducts_SameRegion = CALCULATE([TotalSales], ALL(Products[Category]))
|
|
289
|
+
|
|
290
|
+
// Remove filters except specified columns
|
|
291
|
+
SalesInCategory = CALCULATE(
|
|
292
|
+
[TotalSales],
|
|
293
|
+
ALLEXCEPT(Products, Products[Category])
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
// Modern syntax: REMOVEFILTERS (more explicit)
|
|
297
|
+
AllRegions = CALCULATE([TotalSales], REMOVEFILTERS(Region))
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Percentage Calculations
|
|
301
|
+
|
|
302
|
+
```dax
|
|
303
|
+
// Percentage of Grand Total
|
|
304
|
+
Pct_GrandTotal = DIVIDE(
|
|
305
|
+
[TotalSales],
|
|
306
|
+
CALCULATE([TotalSales], ALL(Sales))
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
// Percentage of Parent (within category)
|
|
310
|
+
Pct_Category = DIVIDE(
|
|
311
|
+
[TotalSales],
|
|
312
|
+
CALCULATE([TotalSales], ALLEXCEPT(Products, Products[Category]))
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
// Percentage of Row Total (in matrix)
|
|
316
|
+
Pct_RowTotal = DIVIDE(
|
|
317
|
+
[TotalSales],
|
|
318
|
+
CALCULATE([TotalSales], ALLSELECTED(Products[SubCategory]))
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
// Percentage of Column Total (in matrix)
|
|
322
|
+
Pct_ColumnTotal = DIVIDE(
|
|
323
|
+
[TotalSales],
|
|
324
|
+
CALCULATE([TotalSales], ALLSELECTED('Date'[Month]))
|
|
325
|
+
)
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### KEEPFILTERS
|
|
329
|
+
|
|
330
|
+
```dax
|
|
331
|
+
// Without KEEPFILTERS - replaces existing filter
|
|
332
|
+
Sales_A = CALCULATE([TotalSales], Products[Category] = "A")
|
|
333
|
+
// If visual is filtered to Category "B", returns sales for "A"
|
|
334
|
+
|
|
335
|
+
// With KEEPFILTERS - intersects with existing filter
|
|
336
|
+
Sales_A_KeepFilters = CALCULATE(
|
|
337
|
+
[TotalSales],
|
|
338
|
+
KEEPFILTERS(Products[Category] = "A")
|
|
339
|
+
)
|
|
340
|
+
// If visual is filtered to Category "B", returns BLANK (no intersection)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Iterator Patterns
|
|
346
|
+
|
|
347
|
+
### SUMX / AVERAGEX / MAXX / MINX
|
|
348
|
+
|
|
349
|
+
```dax
|
|
350
|
+
// Weighted Average
|
|
351
|
+
WeightedAvgPrice = DIVIDE(
|
|
352
|
+
SUMX(Sales, Sales[Quantity] * Sales[UnitPrice]),
|
|
353
|
+
SUM(Sales[Quantity])
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
// Average of Positive Values Only
|
|
357
|
+
AvgPositiveSales = AVERAGEX(
|
|
358
|
+
FILTER(Sales, Sales[Amount] > 0),
|
|
359
|
+
Sales[Amount]
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
// Maximum Sale Amount
|
|
363
|
+
MaxSale = MAXX(Sales, Sales[Amount])
|
|
364
|
+
|
|
365
|
+
// Minimum Non-Zero
|
|
366
|
+
MinNonZero = MINX(
|
|
367
|
+
FILTER(Sales, Sales[Amount] > 0),
|
|
368
|
+
Sales[Amount]
|
|
369
|
+
)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### COUNTX / COUNTAX
|
|
373
|
+
|
|
374
|
+
```dax
|
|
375
|
+
// Count rows meeting condition
|
|
376
|
+
LargeOrderCount = COUNTX(
|
|
377
|
+
FILTER(Sales, Sales[Amount] > 1000),
|
|
378
|
+
1 -- Just counting rows
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
// Count non-blank results
|
|
382
|
+
CustomersWithSales = COUNTAX(
|
|
383
|
+
FILTER(Customers, [CustomerSales] > 0),
|
|
384
|
+
Customers[CustomerKey]
|
|
385
|
+
)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Nested Iterators
|
|
389
|
+
|
|
390
|
+
```dax
|
|
391
|
+
// Average sales per product per region
|
|
392
|
+
AvgSalesProductRegion = AVERAGEX(
|
|
393
|
+
CROSSJOIN(VALUES(Products[Product]), VALUES(Region[Region])),
|
|
394
|
+
[TotalSales]
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
// Use with caution - can be slow on large datasets
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Ranking Patterns
|
|
403
|
+
|
|
404
|
+
### Basic Ranking
|
|
405
|
+
|
|
406
|
+
```dax
|
|
407
|
+
// Dense rank (ties get same rank, no gaps)
|
|
408
|
+
ProductRank = RANKX(
|
|
409
|
+
ALL(Products[Product]),
|
|
410
|
+
[TotalSales],
|
|
411
|
+
, -- Skip value parameter
|
|
412
|
+
DESC,
|
|
413
|
+
DENSE
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
// Standard rank (gaps after ties)
|
|
417
|
+
ProductRank_Standard = RANKX(
|
|
418
|
+
ALL(Products[Product]),
|
|
419
|
+
[TotalSales],
|
|
420
|
+
,
|
|
421
|
+
DESC,
|
|
422
|
+
SKIP
|
|
423
|
+
)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Ranking Within Groups
|
|
427
|
+
|
|
428
|
+
```dax
|
|
429
|
+
// Rank within category
|
|
430
|
+
RankInCategory = RANKX(
|
|
431
|
+
ALLEXCEPT(Products, Products[Category]),
|
|
432
|
+
[TotalSales],
|
|
433
|
+
,
|
|
434
|
+
DESC,
|
|
435
|
+
DENSE
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
// Dynamic ranking (respects slicer selection)
|
|
439
|
+
RankSelected = RANKX(
|
|
440
|
+
ALLSELECTED(Products[Product]),
|
|
441
|
+
[TotalSales],
|
|
442
|
+
,
|
|
443
|
+
DESC,
|
|
444
|
+
DENSE
|
|
445
|
+
)
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### Top N Filtering
|
|
449
|
+
|
|
450
|
+
```dax
|
|
451
|
+
// Is this product in Top 10?
|
|
452
|
+
IsTop10 = IF(
|
|
453
|
+
RANKX(ALL(Products[Product]), [TotalSales], , DESC, DENSE) <= 10,
|
|
454
|
+
1,
|
|
455
|
+
0
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
// Sales from Top 10 Products Only
|
|
459
|
+
Top10ProductSales = CALCULATE(
|
|
460
|
+
[TotalSales],
|
|
461
|
+
TOPN(10, ALL(Products[Product]), [TotalSales], DESC)
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
// Bottom 10
|
|
465
|
+
Bottom10ProductSales = CALCULATE(
|
|
466
|
+
[TotalSales],
|
|
467
|
+
TOPN(10, ALL(Products[Product]), [TotalSales], ASC)
|
|
468
|
+
)
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## Virtual Tables
|
|
474
|
+
|
|
475
|
+
### TREATAS
|
|
476
|
+
|
|
477
|
+
```dax
|
|
478
|
+
// Apply filter from one table to another (no relationship needed)
|
|
479
|
+
BudgetForActualProducts = CALCULATE(
|
|
480
|
+
[TotalBudget],
|
|
481
|
+
TREATAS(VALUES(Sales[ProductKey]), Budget[ProductKey])
|
|
482
|
+
)
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### SUMMARIZE
|
|
486
|
+
|
|
487
|
+
```dax
|
|
488
|
+
// Create summary table
|
|
489
|
+
SalesByCategory = SUMMARIZE(
|
|
490
|
+
Sales,
|
|
491
|
+
Products[Category],
|
|
492
|
+
"Total", SUM(Sales[Amount]),
|
|
493
|
+
"Count", COUNT(Sales[TransactionID])
|
|
494
|
+
)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### ADDCOLUMNS
|
|
498
|
+
|
|
499
|
+
```dax
|
|
500
|
+
// Add calculated columns to virtual table
|
|
501
|
+
ProductsWithSales = ADDCOLUMNS(
|
|
502
|
+
Products,
|
|
503
|
+
"ProductSales", [TotalSales],
|
|
504
|
+
"ProductRank", [ProductRank]
|
|
505
|
+
)
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### SELECTCOLUMNS
|
|
509
|
+
|
|
510
|
+
```dax
|
|
511
|
+
// Select and rename columns
|
|
512
|
+
SimplifiedProducts = SELECTCOLUMNS(
|
|
513
|
+
Products,
|
|
514
|
+
"ID", Products[ProductKey],
|
|
515
|
+
"Name", Products[ProductName],
|
|
516
|
+
"Sales", [TotalSales]
|
|
517
|
+
)
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Error Handling
|
|
523
|
+
|
|
524
|
+
### IFERROR / ISERROR
|
|
525
|
+
|
|
526
|
+
```dax
|
|
527
|
+
// Handle potential errors
|
|
528
|
+
SafeCalculation = IFERROR([PotentiallyBadMeasure], 0)
|
|
529
|
+
|
|
530
|
+
// Check before calculating
|
|
531
|
+
ConditionalCalc = IF(
|
|
532
|
+
ISERROR([PotentiallyBadMeasure]),
|
|
533
|
+
"Error in calculation",
|
|
534
|
+
[PotentiallyBadMeasure]
|
|
535
|
+
)
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### BLANK Handling
|
|
539
|
+
|
|
540
|
+
```dax
|
|
541
|
+
// Replace BLANK with zero
|
|
542
|
+
SalesOrZero = IF(ISBLANK([TotalSales]), 0, [TotalSales])
|
|
543
|
+
|
|
544
|
+
// Shorter syntax
|
|
545
|
+
SalesOrZero = [TotalSales] + 0 // Forces BLANK to 0
|
|
546
|
+
|
|
547
|
+
// Check for BLANK
|
|
548
|
+
HasSales = NOT(ISBLANK([TotalSales]))
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## Anti-Patterns to Avoid
|
|
554
|
+
|
|
555
|
+
### Nested CALCULATE
|
|
556
|
+
|
|
557
|
+
```dax
|
|
558
|
+
-- BAD: Nested CALCULATE
|
|
559
|
+
Bad_Nested = CALCULATE(
|
|
560
|
+
CALCULATE([Sales], Table1[Col] = "A"),
|
|
561
|
+
Table2[Col] = "B"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
-- GOOD: Flat filters
|
|
565
|
+
Good_Flat = CALCULATE(
|
|
566
|
+
[Sales],
|
|
567
|
+
Table1[Col] = "A",
|
|
568
|
+
Table2[Col] = "B"
|
|
569
|
+
)
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### FILTER on Large Tables
|
|
573
|
+
|
|
574
|
+
```dax
|
|
575
|
+
-- BAD: FILTER iterates entire fact table
|
|
576
|
+
Bad_Filter = CALCULATE(
|
|
577
|
+
SUM(FactSales[Amount]),
|
|
578
|
+
FILTER(FactSales, FactSales[Region] = "North")
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
-- GOOD: Filter on dimension
|
|
582
|
+
Good_Filter = CALCULATE(
|
|
583
|
+
SUM(FactSales[Amount]),
|
|
584
|
+
DimRegion[Region] = "North"
|
|
585
|
+
)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Hardcoded Values
|
|
589
|
+
|
|
590
|
+
```dax
|
|
591
|
+
-- BAD: Hardcoded year
|
|
592
|
+
Sales2024 = CALCULATE([Sales], 'Date'[Year] = 2024)
|
|
593
|
+
|
|
594
|
+
-- GOOD: Dynamic
|
|
595
|
+
SalesCurrentYear = CALCULATE([Sales], 'Date'[Year] = YEAR(TODAY()))
|
|
596
|
+
|
|
597
|
+
-- BETTER: Parameter table
|
|
598
|
+
SalesSelectedYear = CALCULATE([Sales], 'Date'[Year] = SELECTEDVALUE(YearParameter[Year]))
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### Calculated Columns for Display
|
|
602
|
+
|
|
603
|
+
```dax
|
|
604
|
+
-- BAD: Calculated column for values that should be measures
|
|
605
|
+
-- (increases model size, can't be filtered dynamically)
|
|
606
|
+
Sales[Running_Total] = ... -- Don't do this
|
|
607
|
+
|
|
608
|
+
-- GOOD: Measure instead
|
|
609
|
+
Running_Total = CALCULATE(...) -- Use measures
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
## Performance Tips
|
|
615
|
+
|
|
616
|
+
1. **Variables cache results** - Calculate once, use many times
|
|
617
|
+
2. **Filter on dimensions** - Not fact tables
|
|
618
|
+
3. **Simple predicates first** - Column = Value is faster than FILTER
|
|
619
|
+
4. **Limit iterator scope** - Filter before iterating
|
|
620
|
+
5. **Avoid bi-directional** - Use measures instead
|
|
621
|
+
6. **Use SUMMARIZE carefully** - Can be slow; SUMMARIZECOLUMNS is internal only
|
|
622
|
+
7. **Test with DAX Studio** - Check Server Timings
|
|
623
|
+
|
|
624
|
+
### Performance Comparison
|
|
625
|
+
|
|
626
|
+
| Pattern | Speed | Use When |
|
|
627
|
+
|---------|-------|----------|
|
|
628
|
+
| `Column = Value` | Fast | Simple equality |
|
|
629
|
+
| `FILTER(dimension, ...)` | Medium | Complex conditions |
|
|
630
|
+
| `FILTER(fact, ...)` | Slow | Avoid if possible |
|
|
631
|
+
| `Nested CALCULATE` | Slow | Never |
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Formatting Standards
|
|
636
|
+
|
|
637
|
+
```dax
|
|
638
|
+
-- Simple measure (one line)
|
|
639
|
+
TotalSales = SUM(Sales[Amount])
|
|
640
|
+
|
|
641
|
+
-- Medium complexity (few lines)
|
|
642
|
+
Margin = DIVIDE([Profit], [Revenue], 0)
|
|
643
|
+
|
|
644
|
+
-- Complex measure (structured with comments)
|
|
645
|
+
ComplexMeasure =
|
|
646
|
+
// Purpose: Calculate year-over-year change with handling for missing prior year
|
|
647
|
+
VAR _CurrentSales = [TotalSales]
|
|
648
|
+
VAR _PriorYearSales =
|
|
649
|
+
CALCULATE(
|
|
650
|
+
[TotalSales],
|
|
651
|
+
SAMEPERIODLASTYEAR('Date'[Date])
|
|
652
|
+
)
|
|
653
|
+
VAR _Change = _CurrentSales - _PriorYearSales
|
|
654
|
+
VAR _HasPriorYear = NOT(ISBLANK(_PriorYearSales))
|
|
655
|
+
RETURN
|
|
656
|
+
IF(
|
|
657
|
+
_HasPriorYear,
|
|
658
|
+
DIVIDE(_Change, _PriorYearSales),
|
|
659
|
+
BLANK()
|
|
660
|
+
)
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Quick Reference
|
|
666
|
+
|
|
667
|
+
| Need | DAX |
|
|
668
|
+
|------|-----|
|
|
669
|
+
| Sum | `SUM(Table[Column])` |
|
|
670
|
+
| Count rows | `COUNTROWS(Table)` |
|
|
671
|
+
| Distinct count | `DISTINCTCOUNT(Table[Column])` |
|
|
672
|
+
| Average | `AVERAGE(Table[Column])` |
|
|
673
|
+
| Safe division | `DIVIDE(num, denom, alt)` |
|
|
674
|
+
| Year-to-date | `TOTALYTD([Measure], 'Date'[Date])` |
|
|
675
|
+
| Prior year | `CALCULATE([Measure], SAMEPERIODLASTYEAR('Date'[Date]))` |
|
|
676
|
+
| Remove filters | `CALCULATE([Measure], ALL(Table))` |
|
|
677
|
+
| Keep some filters | `CALCULATE([Measure], ALLEXCEPT(Table, Table[Col]))` |
|
|
678
|
+
| Rank | `RANKX(ALL(Table[Col]), [Measure], , DESC, DENSE)` |
|
|
679
|
+
| Top N | `CALCULATE([Measure], TOPN(N, ALL(Table[Col]), [Measure]))` |
|
|
680
|
+
| Rolling 12M | `CALCULATE([Measure], DATESINPERIOD('Date'[Date], MAX('Date'[Date]), -12, MONTH))` |
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## Complexity Adaptation
|
|
685
|
+
|
|
686
|
+
Adjust depth based on `config.json → experienceLevel`:
|
|
687
|
+
- **beginner**: Step-by-step with explanations, reference library examples
|
|
688
|
+
- **intermediate**: Standard depth, explain non-obvious decisions
|
|
689
|
+
- **advanced**: Concise, skip basics, focus on edge cases and optimization
|
|
690
|
+
|
|
691
|
+
## Related Skills
|
|
692
|
+
|
|
693
|
+
- `/power-query` — Data transformation before DAX
|
|
694
|
+
- `/data-modeling` — Model design affects DAX patterns
|
|
695
|
+
- `/query-performance` — Optimize DAX performance
|
|
696
|
+
- `/testing-validation` — Test DAX measures
|
|
697
|
+
- `/dax-doctor` — Debug DAX issues
|
|
698
|
+
- `/dax-udf` — User-defined functions for reusable DAX logic
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## Related Resources
|
|
703
|
+
|
|
704
|
+
- [Snippets: Time Intelligence](../../snippets/dax/time-intelligence.md)
|
|
705
|
+
- [Snippets: Rankings](../../snippets/dax/rankings-and-topn.md)
|
|
706
|
+
- [Snippets: CALCULATE Patterns](../../snippets/dax/calculate-patterns.md)
|
|
707
|
+
- [Query Performance Skill](../query-performance/SKILL.md)
|
|
708
|
+
- [Data Modeling Skill](../data-modeling/SKILL.md)
|