power-bi-dax
Skill0 starsChanged 41 days agoMITView on GitHub
Writes correct, fast DAX measures with proper filter context, time intelligence on a marked date table, and VertiPaq-friendly patterns, diagnosing slow visuals down to the storage-vs-formula engine split. Use when someone asks "why does my DAX measure return the wrong total", "write a YTD or year-over-year measure", "my Power BI report is slow", "CALCULATE isn't doing what I expect", or "measure vs calculated column". Do NOT use for Tableau calculated fields and dashboard design - use tableau-best-practices instead; for writing warehouse SQL and turning query results into findings - use sql-to-insights instead; for Excel or Google Sheets financial models - use spreadsheet-model-builder instead.
In this repository3 other files it gives its agents.
- data-storySkill
- sql-to-insightsSkill
- tableau-best-practicesSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. About 0 are common, 11 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈11
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 35
Use variables for clarity and speed - evaluated once and reused:
- Very specific: line 44
Always use DIVIDE instead of / to avoid divide-by-zero errors.
- Distinctive: line 59
If a time-intelligence measure returns blanks or wrong periods, check the three date-table conditions from Step 1 before touching the DAX - …
- Distinctive: line 83
Prefer integer keys over strings.
- Distinctive: line 84
Avoid bidirectional relationships unless genuinely required.
- Distinctive: line 85
Avoid iterators over large fact tables (10M+ rows) that reference measures per row.
- Very specific: line 86
Use SELECTEDVALUE instead of HASONEVALUE + VALUES patterns.
- Distinctive: line 126
Do not use calculated columns for aggregations that belong in measures.
- Very specific: line 127
Do not filter whole tables inside CALCULATE when a column predicate works.
- Distinctive: line 128
Do not average percentage columns in visuals.
- Distinctive: line 129
Do not build time intelligence without a marked, contiguous Date table.
- Distinctive: line 130
Do not reference measures inside iterators over large tables without checking the timing split.
- Distinctive: line 134
Every measure verified at the total level and under at least two slicer combinations, not just the detail rows.
- Very specific: line 135
No division uses /; all use DIVIDE.
- Distinctive: line 137
No visual over 1 second without a documented diagnosis.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Power BI DAX
DAX fails quietly: a measure that returns plausible numbers under one slicer combination and wrong ones under another, or a report that takes 20 seconds because one measure iterates a fact table row by row. This skill produces measures that are correct under every filter context and fast in VertiPaq, and gives a procedure for diagnosing the ones that are not.
Operating procedure
Step 1: Gather inputs
- The model schema: fact and dimension tables, relationships and their directions, and row counts on the facts.
- Whether a dedicated Date table exists, is marked as a date table, and covers a contiguous range - time intelligence silently misbehaves without all three.
- The business definition of each measure in one sentence, including what it should do under totals and slicers ("margin % weighted by revenue, respecting the category slicer").
- Performance symptoms if any: which visual, which page, how slow. Default target: any single visual renders in under 1 second; anything over 3 seconds is a defect to diagnose, not a fact of life.
Step 2: Choose measure vs calculated column
Prefer measures. They evaluate in the report's filter context and do not bloat the model. Use calculated columns only when you need a row-level value for slicing or relationships. Every calculated column is materialized and compressed into the model; every aggregation belongs in a measure.
Step 3: Write the measure with context in mind
Two contexts exist: filter context (slicers, rows, columns, CALCULATE) and row context (calculated columns, iterators like SUMX). CALCULATE is the only function that turns row context into filter context via context transition. A measure referenced inside SUMX is wrapped in an implicit CALCULATE - that context transition per row is both a correctness feature and a performance trap.
Base measure:
Total Sales = SUM ( Sales[Amount] )Use variables for clarity and speed - evaluated once and reused:
Margin % =
VAR Revenue = [Total Sales]
VAR Cost = SUM ( Sales[Cost] )
RETURN DIVIDE ( Revenue - Cost, Revenue )Always use DIVIDE instead of / to avoid divide-by-zero errors.
Step 4: Time intelligence on a marked date table
Sales YTD =
CALCULATE ( [Total Sales], DATESYTD ( 'Date'[Date] ) )
Sales PY =
CALCULATE ( [Total Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
Sales YoY % =
DIVIDE ( [Total Sales] - [Sales PY], [Sales PY] )If a time-intelligence measure returns blanks or wrong periods, check the three date-table conditions from Step 1 before touching the DAX - that is the cause in the large majority of cases.
Step 5: Control filter context deliberately
% of Total =
DIVIDE (
[Total Sales],
CALCULATE ( [Total Sales], ALLSELECTED ( Product[Category] ) )
)ALLremoves all filters from a column or table.ALLSELECTEDrespects outer slicers but ignores inner row/column grouping.KEEPFILTERSintersects rather than overrides a filter inside CALCULATE.REMOVEFILTERSis the modern alias for clearing filters.
Pick by asking: should the denominator move when the user slices? ALLSELECTED if yes, ALL if never.
Step 6: Diagnose performance
Run Performance Analyzer, then copy the slow query into DAX Studio with server timings on. Read the split: the storage engine (VertiPaq scans) should dominate. When the formula engine consumes more than ~50% of query time, the measure is doing row-by-row work the storage engine cannot compress - rewrite it. Model-side levers, in order of payoff:
- Reduce cardinality of columns used in relationships; split datetime into separate date and time columns. A column with over ~1 million distinct values is a compression red flag.
- Prefer integer keys over strings.
- Avoid bidirectional relationships unless genuinely required; they multiply scan work and create ambiguity.
- Avoid iterators over large fact tables (10M+ rows) that reference measures per row; restate the logic as column arithmetic inside one SUMX, or pre-aggregate.
- Use
SELECTEDVALUEinstead ofHASONEVALUE+VALUESpatterns.
Worked artifact: bad/good measure pair
Business ask: sales for red products, respecting all other slicers.
Bad - iterates the entire fact table in the formula engine and forces a context transition per row:
Red Sales (Bad) =
CALCULATE (
[Total Sales],
FILTER ( Sales, RELATED ( Product[Color] ) = "Red" )
)Good - a column predicate VertiPaq resolves as a compressed scan:
Red Sales (Good) =
CALCULATE ( [Total Sales], KEEPFILTERS ( Product[Color] = "Red" ) )On a 50M-row fact table the bad version commonly runs seconds in the formula engine; the good version runs tens of milliseconds in the storage engine. Same numbers, two orders of magnitude apart. (KEEPFILTERS preserves an existing user filter on Color; drop it only if the measure must override the slicer.)
Second pair - weighted math. Bad: a calculated column Sales[Margin %] per row, then averaged in the visual - wrong weighting (simple average of row percentages) and model bloat. Good:
Weighted Margin =
SUMX ( Sales, Sales[Qty] * ( Sales[Price] - Sales[Cost] ) )Row-by-row math belongs in an iterator inside a measure, using column arithmetic rather than nested measure references.
Deliverable
Produce a measure set where each measure has: the DAX, a one-sentence business definition, the expected behavior under totals and slicers, and - for any measure flagged slow - the DAX Studio storage/formula engine split before and after the rewrite.
Do NOT
- Do not use calculated columns for aggregations that belong in measures; the model pays in size and refresh time forever.
- Do not filter whole tables inside CALCULATE when a column predicate works;
FILTER ( Sales, ... )forces formula-engine iteration. - Do not average percentage columns in visuals; compute ratios as measures so weighting is correct at every level.
- Do not build time intelligence without a marked, contiguous Date table; the failure is silent wrong numbers, not an error.
- Do not reference measures inside iterators over large tables without checking the timing split; implicit context transition per row is the classic hidden cost.
Quality bar
- Every measure verified at the total level and under at least two slicer combinations, not just the detail rows.
- No division uses
/; all use DIVIDE. - Date table is marked, contiguous, and every time-intelligence measure filters through it.
- No visual over 1 second without a documented diagnosis; formula engine share under ~50% on the measures that matter.
- Model has no unexplained bidirectional relationships and no string keys on large relationships.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

