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