This skill should be used when the user asks to "write DAX measures", "create Power BI calculations", "help with DAX formulas", "write time intelligence", or mentions aggregations, filters, or DAX performance. Ensures correct syntax, optimal performance, and best practices on the first attempt.
Install with Tessl CLI
npx tessl i github:back1ply/LLM-Skills --skill writing-dax-measuresOverall
score
84%
Does it follow best practices?
If you maintain this skill, you can automatically optimize it using the tessl CLI to improve its score:
npx tessl skill review --optimize ./path/to/skillValidation for skill structure
Generate correct DAX measures for Power BI on the first attempt by following pre-generation validation, avoiding anti-patterns, and applying proven best practices.
Core principle: Prompt quality exceeds model size. Accurate schema + avoiding anti-patterns + following best practices = first-try success.
Applicable to DAX measure creation, Power BI calculations, DAX formulas, and aggregations/filters/time intelligence in Power BI context.
Not applicable to SQL queries, Excel formulas, Python/R data analysis, or other BI tools (Tableau, Qlik).
BEFORE writing any DAX code, complete these steps in order:
Never use these functions in new DAX code:
| Function | Why Forbidden | Use Instead |
|---|---|---|
EARLIER() | Deprecated, hard to maintain | VAR to save row context values |
IFERROR() | Unreliable (misses errors), kills performance | DIVIDE() for division; design away errors |
ISERROR() | Same issues as IFERROR | DIVIDE() for division; design away errors |
FIRSTNONBLANK() | Poor performance (iterator) | MIN() or MAX() on column |
LASTNONBLANK() | Poor performance (iterator) | MIN() or MAX() on column |
| Don't Use | DAX Alternative |
|---|---|
SUMIF, COUNTIF, AVERAGEIF | CALCULATE([Measure], filter) |
VLOOKUP, HLOOKUP | RELATED() or LOOKUPVALUE() |
SAMEPERIODLASTDAY | Not a DAX function (typo of SAMEPERIODLASTYEAR) |
Never do these:
❌ Naked CALCULATE without filters
CALCULATE([Sales]) -- Does nothing useful
❌ Division operator / (no error handling)
[Sales] / [Quantity] -- Fails on zero
❌ FILTER on fact tables with scalar comparisons
FILTER(Sales, Sales[Amount] > 100) -- Slow on large tables
❌ Filtering entire tables instead of columns
CALCULATE([Sales], FILTER(Products, ...)) -- Very slow
❌ Measures that never return BLANK
IF(ISBLANK([Sales]), 0, [Sales]) -- Hurts compression
❌ IF conditions inside iterators
SUMX(Sales, IF(Sales[Category] = "A", Sales[Amount], 0)) -- Use CALCULATE❌ BAD - Filters entire table
CALCULATE([Sales], FILTER(ALL(Products), Products[Category] = "Electronics"))
✅ GOOD - Filters column directly
CALCULATE([Sales], Products[Category] = "Electronics")❌ BAD - Complex logic first, then filter
VAR Result = [ComplexCalculation]
RETURN CALCULATE(Result, 'Date'[Year] = 2024)
✅ GOOD - Filter first, then calculate
CALCULATE([ComplexCalculation], 'Date'[Year] = 2024)Additional Filter Rules:
CALCULATETABLE instead of FILTER for table operationsREMOVEFILTERS instead of ALL for clarityVariables improve performance AND readability:
✅ Use VAR to:
- Avoid repetitive calculations (computed only once)
- Improve code readability
- Replace EARLIER pattern (deprecated)
- Store intermediate results
- Optimize IF conditions
- Cache measure values outside iteratorsExample:
Total Sales Ratio =
VAR TotalSales = SUM(Sales[Amount])
VAR AllSales = CALCULATE(TotalSales, ALL(Sales))
RETURN
DIVIDE(TotalSales, AllSales)Iterators are expensive - minimize usage:
SUM >> SUMXSUMMARIZE or GROUPBY to reduce rowsCALCULATE with filter instead❌ BAD - Iterator with IF condition
SUMX(Sales, IF(Sales[Amount] > 100, Sales[Amount], 0))
✅ GOOD - Filter first, then iterate
SUMX(FILTER(Sales, Sales[Amount] > 100), Sales[Amount])
✅ BETTER - Use CALCULATE if possible
CALCULATE(SUM(Sales[Amount]), Sales[Amount] > 100)Measures are automatically wrapped in CALCULATE
Context transition is expensive
Filter arguments of CALCULATE don't receive context transition
✅ Use SELECTEDVALUE() instead of VALUES()
-- VALUES() errors on multiple values
-- SELECTEDVALUE() returns BLANK on multiple values
✅ Be explicit about context
CALCULATE([Measure], FILTER(...)) -- Clear intent
✅ Cache measure values outside iterators
VAR MeasureValue = [Measure]
RETURN SUMX(Table, MeasureValue * Table[Column])The date table MUST satisfy these requirements:
✅ Continuous dates: All dates from Jan 1 to Dec 31 for all years ✅ Marked as Date Table: Apply "Mark as Date Table" setting in Power BI ✅ One-to-many relationship: Date table → Fact table ✅ No gaps: Every single day present in range
# Year-to-Date
Sales YTD =
CALCULATE(
[Total Sales],
DATESYTD('Date'[Date])
)
# Previous Year
Sales PY =
CALCULATE(
[Total Sales],
SAMEPERIODLASTYEAR('Date'[Date])
)
# Year-over-Year Growth %
YoY Growth % =
VAR CurrentYear = [Total Sales]
VAR PreviousYear = [Sales PY]
RETURN
DIVIDE(CurrentYear - PreviousYear, PreviousYear)
# Month-to-Date
Sales MTD =
CALCULATE(
[Total Sales],
DATESMTD('Date'[Date])
)
# Quarter-to-Date
Sales QTD =
CALCULATE(
[Total Sales],
DATESQTD('Date'[Date])
)CRITICAL RULE: Use time intelligence functions only in filter arguments of CALCULATE, never directly in iterators (triggers expensive context transitions).
Understanding these functions is critical for correct filter behavior:
| Function | Behavior | Use When |
|---|---|---|
ALL(Table) | Ignores ALL filters from everywhere | Global calculations, grand totals |
ALLSELECTED(Table) | Ignores filters from within visual, keeps external filters | Dynamic totals respecting slicers |
ALLEXCEPT(Table, Col1, Col2) | Removes all filters EXCEPT specified columns | Preserve specific dimensions |
REMOVEFILTERS(Table[Column]) | Clear syntax for removing filters | Preferred over ALL (better readability) |
Default choice: Use ALLSELECTED for user-friendly dashboards with slicers.
Examples:
# Grand Total (ignores ALL filters)
Grand Total Sales =
CALCULATE([Total Sales], ALL(Sales))
# Total Respecting Slicers (ignores only visual filters)
Dynamic Total =
CALCULATE([Total Sales], ALLSELECTED(Sales))
# Total by Region (keeps Region filter, removes others)
Total by Region =
CALCULATE([Total Sales], ALLEXCEPT(Sales, Sales[Region]))
# Clear specific filter (recommended syntax)
Sales Without Date Filter =
CALCULATE([Total Sales], REMOVEFILTERS('Date'))✅ Let measures return BLANK naturally
-- Don't replace BLANK with 0 unless required
-- BLANK enables VertiPaq compression optimization
✅ Use DIVIDE() for safe division
DIVIDE([Numerator], [Denominator], 0) -- Returns 0 on division by zero
✅ Use COALESCE() for first non-blank value
COALESCE([Measure1], [Measure2], [Measure3], 0)
✅ Use ISBLANK() to check before calculations
IF(ISBLANK([Measure]), 0, [Measure]) -- Only when UI requires no blanks
❌ Never create measures that never return BLANK
-- Kills VertiPaq compression and performanceDefault: Always use Measures unless you need physical column structure.
| Criterion | Measure | Calculated Column |
|---|---|---|
| Evaluation | Query time (dynamic) | Refresh time (static) |
| Storage | No storage (code only) | Stored in model (uses RAM) |
| Performance | Better (on-demand calculation) | Slower refresh, consumes memory |
| Filter Context | Respects slicers/filters | Static per row |
| Use Cases | Aggregations, dynamic calculations | Slicers, row-level filters, static lookups |
Use Calculated Columns only when:
For relationships (USERELATIONSHIP, CROSSFILTER), running totals, rankings, moving averages, and percentage calculations, see patterns.md.
✅ Measures: [Total Sales]
✅ Columns: Products[Category]
✅ Tables: Sales (be consistent: singular OR plural)
✅ Clear names: [Year-over-Year Growth %]
❌ Abbreviations: [YoY_Grth_Pct]✅ Add comments for complex logic
-- Calculate active customer sales only
-- Customers with purchases in last 90 days
✅ Use descriptive variable names
VAR ActiveCustomers = ... -- Not just "AC"
✅ Format code for readability
-- Use DAX Formatter (daxformatter.com)Avoid: Technical prefixes (dim_, fact_), cryptic abbreviations, unclear variable names
For debugging tools (DAX Studio, Tabular Editor), techniques, common errors and fixes, see debugging.md.
BEFORE submitting DAX code to user, verify:
□ All functions exist in DAX (no Excel/SQL functions)
□ All tables/columns match live schema exactly (case-sensitive)
□ No deprecated functions (EARLIER, IFERROR, ISERROR, FIRSTNONBLANK, LASTNONBLANK)
□ Context transitions are explicit and optimized
□ Variables used for repetitive calculations
□ DIVIDE() used instead of / operator
□ Filters applied to columns, not tables
□ Time intelligence functions only in filter arguments of CALCULATE
□ Measure naming follows convention: [Measure Name]
□ Comments added for complex logic
□ Code formatted for readability| Task | DAX Pattern |
|---|---|
| Safe division | DIVIDE([Num], [Denom], 0) |
| Remove filters | REMOVEFILTERS(Table[Column]) |
| Remove all filters | ALL(Table[Column]) |
| Keep slicer filters | ALLSELECTED(Table) |
| Store value | VAR MyVal = [Measure] then use MyVal |
| Single value from filter | SELECTEDVALUE(Table[Column]) |
| Filter by value | CALCULATE([Measure], Table[Column] = "Value") |
| Year-to-date | CALCULATE([Measure], DATESYTD('Date'[Date])) |
| Previous year | CALCULATE([Measure], SAMEPERIODLASTYEAR('Date'[Date])) |
| Running total | CALCULATE([Measure], FILTER(ALL('Date'), 'Date'[Date] <= MAX('Date'[Date]))) |
| Ranking | RANKX(ALL(Table[Column]), [Measure], , DESC, DENSE) |
| Check if blank | IF(ISBLANK([Measure]), [Alternative], [Measure]) |
| First non-blank | COALESCE([Measure1], [Measure2], 0) |
| Related value | RELATED(DimTable[Column]) |
| Lookup value | LOOKUPVALUE(Table[Return], Table[Search], SearchValue) |
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.