Power BI models rarely break in one day. They grow. A measure is copied to test an idea, a column is imported just in case, a page is duplicated for a meeting. Nobody deletes anything, because nobody knows what is safe to delete. A year later the model is slow to refresh, hard to change and full of things no one can explain.
So we built a free Power BI Model Health Check. To show what it finds on a model anyone can download and check for themselves, we ran it on one of Microsoft's own public samples: the COVID-19 US Tracking Sample, published under the MIT licence. It scored 84 out of 100. Here is what it found, and what each finding means for any model.
The model we checked
4 tables, 16 columns, 10 measures and 2 relationships, and a report with 2 pages and 67 visuals. A small, published model, and it still has findings: every model collects them as it changes. The whole check took well under a second.
- 4 of 16 columns (25%) are not used anywhere: not in a visual, a filter, a measure, a relationship, a sort order or a security rule.
- 2 of 12 bookmarks still refer to a column that no longer exists in the model.
- Auto date/time is on, so Power BI keeps a hidden date table for the date column.
- 4 calculated columns on imported tables, three of which read other tables or rows.
- Plus smaller things: one unused measure, two visible key columns, a relationship on a text key, and four measures without a format string.
1. A quarter of the columns are never used
Every imported column costs memory and refresh time, whether anyone looks at it or not. Text columns with many unique values, like names, notes and IDs, cost the most. Here the unused ones are a county code (FIPS), a flag for US territories and two columns of a hidden helper table. In a small sample that costs little; in a model with millions of rows, one unused ID column can take a large share of its size.
The fix is to remove them in Power Query, not to hide them. A hidden column is still loaded and stored. The health check writes the Table.RemoveColumns step for each table for you. One caution: "unused" means unused in this report. If other reports connect to the same model, check those first.
2. Two bookmarks point at a column that no longer exists
This is the kind of finding nobody looks for. The model has a County Name column, but two bookmarks, "Blue States" and "Pink States", still refer to a County Name - Copy column that is no longer there. The pages look fine: the stale reference sits inside the bookmarks. When a column or measure is renamed or deleted, Power BI does not warn you about every visual, filter or bookmark that used it.
The check lists each broken field and where it is used, so the fix takes minutes: update the bookmark or the visual, or replace the field.
3. Auto date/time is still on
With Auto date/time on, Power BI Desktop creates a hidden date table for each date column of an imported table that isn't on the "many" side of a relationship. Here that is one table. In a real model with many date columns these hidden tables add up: Microsoft's own guidance says each one increases the model size and extends refresh time.
The fix: add one date table and mark it (our free calendar generator writes one), move your visuals to it, then turn the option off in File > Options and settings > Options > Current File > Data Load > Time intelligence. Turning it off removes the hidden tables, so check every visual that used them first.
4. Small things that add up
- Calculated columns on imported tables. Four of them: a county label built with RELATED, daily cases and daily deaths worked out from the day before, and a column that always says "USA". Microsoft's guidance prefers columns added in Power Query or at the source, which typically compress better and don't wait for every table to load; a column that needs measures or DAX-only functions can still be the better choice in DAX.
- Visible keys and a text key. Two key columns on the "many" side are visible in the field list, where report authors drag them in by mistake, and one relationship joins on a text code instead of a number.
- A measure nothing uses. One of the 10 measures feeds no visual, directly or through other measures. The check follows these chains, which is almost impossible to do by hand.
- Measures without a format string. Four here, but all four return text (notes and button labels), so in this model they are harmless. On a number, a missing format is what makes a card show 0.34 for a margin of 33.8% (0.3378): no percent sign, and two decimals that round the value.
What it did not find: FILTER over a whole table. This sample has none, but we see the pattern often, because it is the first way most of us learned to write a filtered measure:
// Scans every row of the table
Online Sales =
CALCULATE ( [Sales], FILTER ( Sales, Sales[Channel] = "Online" ) )
// Filters one column: much faster
Online Sales =
CALCULATE ( [Sales], KEEPFILTERS ( Sales[Channel] = "Online" ) )
FILTER(Table, ...) as a CALCULATE filter builds a table of every column of every row that passes, and applies all of it. A filter on the one column you care about lets the engine work on that column alone. In almost every model the result is the same, and KEEPFILTERS keeps it that way when a slicer is already filtering that column. Without it, the column filter would replace the slicer.
How the check works, and why your file stays with you
The tool reads a Power BI template (.pbit). A template has your model and report layout but no data rows, so it stays small even for very large models. The file is opened inside your browser tab and never uploaded. The tool maps every measure to the columns and measures it depends on, then follows every visual, filter, relationship and security rule to see what is really used.
Check your own model in three steps
- In Power BI Desktop: File > Export > Power BI template.
- Drop the .pbit on the Model Health Check.
- Start with the "Fix these first" list. Then copy the cleanup scripts and download the documentation for your team.
Microsoft's sample is a demo, so these findings cost it little. A production model has more tables, more history and more people editing it, and usually more to find. The first check takes one minute, and it will likely surprise you.