Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

What counts as unused

ripbi scan starts with the fields used by the reports it can read, then follows the model expressions those fields depend on. An object is reported unused when no path from those report bindings reaches it.

For example, if a visual uses measure [B] and [B] uses [A], both measures are live. If nothing in the reports reaches [B], both can be reported unused, even though [B] refers to [A]. The finding describes the model and reports that ripbi analyzed; it is a starting point for review, not a deletion command.

This page summarizes the rules. Graph and liveness rules is the full specification.

What keeps an object alive

The starting points, called roots:

  • Report bindings: every field a visual, filter, slicer, sort, conditional format, drillthrough page, or bookmark uses — in the desktop layout and the phone layout alike.
  • Row-level security roles: their filters and the tables and columns they grant or revoke. Roles are never findings themselves.
  • ripbi_keep annotations: an object the model marks as used by something ripbi cannot see. See Keeping objects on purpose.
  • Logged queries: objects that DAX or MDX queries in a workspace-monitoring log name. See Logged queries.

From the roots, liveness spreads along these paths:

  • DAX references. A live measure, calculated column, calculation item, KPI, or function keeps everything its DAX names alive. When a bare [Name] could mean several objects, all of them stay alive.
  • Tables and their members. A live column, measure, or hierarchy keeps its table alive. The reverse does not hold: a live table does not keep its ordinary columns alive, which is why unused columns in used tables are the most common finding. A live table does keep its partitions and engine-computed columns (calculated-table and calculation-group columns).
  • Sort-by, group-by, and hierarchy levels. A live column keeps its sort-by and group-by columns; a live hierarchy keeps its level columns.
  • Relationships. A relationship is live when either of its tables is. An active relationship keeps both key columns alive. An inactive one is live only when live DAX switches it on with USERELATIONSHIP. A relationship never keeps a table alive, so a table that nothing but a relationship touches is unused.
  • Power Query. A query that reads another query or a parameter, or merges another table, keeps it alive — removing it would break refresh. A column named in Power Query is not kept alive: the query can keep producing the column while the model stops loading it. Such findings can show a Power Query also names it note (with --power-query).
  • Report measures and bookmarks are ordinary objects: an unused report measure is a finding, and so is a bookmark that only points at deleted pages.

Where a rule could go either way, it keeps the object alive. A false "used" only costs a missed cleanup; a false "unused" would suggest deleting something that is needed.

Reading a finding

Each finding says why nothing keeps it alive:

AnnotationMeaning
← nothing references itNo object refers to it at all.
← only used by X (also unused)Every object that refers to it is itself unused, so the whole chain can go.
← used by XThe user is live, but its use cannot keep this object alive — for example a key column of an inactive relationship.

Human output describes every annotation and section.

Two other verdicts

  • Auto date/time. The hidden LocalDateTable_* tables that Power BI creates for date columns are always technically reachable, so the scan asks a separate question: does any report use them? Each table gets one verdict: in use, unused by reports, or dead.
  • Broken bindings. The scan also reports the opposite problem: report fields and DAX expressions that name something the model no longer has. It flags these only when sure, because a false "broken" is as misleading as a false "unused".

What the scan cannot see

Findings cover only the reports and logs you give the scan. Thin reports in other workspaces, Analyze in Excel, XMLA clients, and other datasets' DAX are invisible unless you include their reports, mark what they use with ripbi_keep, or pass a query log. A model with no connected reports is refused, because without roots every object would come back unused. See known boundaries before deleting anything, and use ripbi deps to see what a finding relies on and what relies on it.