Guides

Which Power BI reports break when this Databricks table changes?

Someone wants to rename a column, drop a table or move a schema in Unity Catalog. The question is which Power BI semantic models read it. Neither product answers that out of the box. This guide builds the answer from both sides: the Power Query code in your models, and the Databricks system tables that record what Power BI actually read.

Why the tools you have do not answer it

  • Power BI lineage stops at the server. Lineage view and impact analysis in the Power BI service work at the level of a data source and its connection string. For a Databricks source that means the workspace host and HTTP path, not the tables a model reads.
  • Unity Catalog lineage does not see Power BI by itself. A Power BI model only appears downstream of a table if you register it through Unity Catalog's external lineage API, by hand or with your own code. External lineage is also not recorded in the lineage system tables.
  • Microsoft Purview does not stitch the two. Its Power BI lineage connects to Azure SQL Database, Blob Storage and Data Lake Storage. Databricks is not on that list.

So the map has to be built. There are two sources of truth, and you want both: what each model is written to read, and what Power BI really read.

Side 1: what each model is written to read

When a query uses the connector's navigation (catalog, then schema, then table), the table name sits in the model's Power Query code as steps like {[Name="sales",Kind="Schema"]}. Our free read-only script collects them. It is the same script as the ODBC to ADBC guide; version 1.2 adds the tables.

powershell -ExecutionPolicy Bypass -File .\find-databricks-odbc.ps1 -Workspace -OutCsv .\databricks-map.csv

Use -Path for PBIP or .pbix files on disk, -Workspace if you are Contributor or higher on the workspaces (no admin rights needed), or -Tenant if you are a Fabric administrator. The prerequisites table explains which one you can run.

Each row of the CSV is one Databricks connection, with these columns among others:

  • SemanticModel and Location: the model and the table or query inside it.
  • Objects: the Unity Catalog tables or views it navigates to, as catalog.schema.table.
  • Host, HttpPath, Compute: the Databricks workspace, and whether it points at a SQL warehouse or a cluster.
  • NativeSql: true when the query is hand-written SQL.

Before changing a table, filter Objects for its name. That is your first list of models to warn.

What this side misses: hand-written SQL (Value.NativeQuery or Databricks.Query), where the tables live inside the SQL text; hosts or paths passed in as parameters; and views, which hide the tables underneath them. The script flags the first two so you can review them by hand. Side 2 covers what it cannot see.

Side 2: what Power BI really read

Databricks records queries in system.query.history and table reads in system.access.table_lineage. The lineage table has a statement_id column, which the documentation describes as a foreign key to query history. It is filled for queries on SQL warehouses and serverless compute. Join the two, and you get the tables that Power BI's queries touched, including the ones hidden in native SQL.

First, find out how Power BI identifies itself on your account. The documentation gives Power BI as an example value of client_application, but not the exact string, so check rather than guess:

SELECT client_application, count(*) AS statements
FROM system.query.history
WHERE start_time >= current_timestamp() - INTERVAL 7 DAYS
GROUP BY client_application
ORDER BY statements DESC;

Then list the tables Power BI read in the last 30 days, with who ran the query and when it last happened:

SELECT l.source_table_full_name AS table_name,
       q.executed_by,
       count(DISTINCT q.statement_id) AS statements,
       max(q.start_time) AS last_read
FROM system.query.history AS q
JOIN system.access.table_lineage AS l
  ON l.statement_id = q.statement_id
WHERE q.client_application = 'Power BI'   -- use the exact value from the first query
  AND q.start_time >= current_timestamp() - INTERVAL 30 DAYS
  AND l.event_time >= current_timestamp() - INTERVAL 30 DAYS
  AND l.source_table_full_name IS NOT NULL
  AND l.target_type IS NULL                -- reads only
GROUP BY l.source_table_full_name, q.executed_by
ORDER BY last_read DESC;

Before a change to one table, add AND l.source_table_full_name = 'catalog.schema.table'.

What this side misses, and why you still need side 1:

  • No model name. Query history knows the identity that ran the query, usually a gateway or service account, not which semantic model sent it. Side 1 gives you the model.
  • Import models only show up at refresh time. A model refreshed weekly may not appear in a short window. Use a window longer than your slowest refresh schedule.
  • Lineage is a subset of events. The documentation says the lineage tables capture a subset of all read and write events, and keep a rolling one-year window.
  • Access. System tables have to be granted to you by an account or metastore admin. The query text is redacted unless you are an account admin or in the group allowed to see it; the queries above do not need it.

Put the two lists side by side

The table is inWhat it usually meansWhat to do
Both listsA live dependency, and you know which modelWarn the model owner and test the change against that model
Side 1 onlyThe model reads it but has not refreshed in the window, or the model is no longer usedCheck the model's last refresh; it may be a candidate to retire
Side 2 onlyHand-written SQL, a view, or a model you could not scanFind the model through the identity in executed_by and the native SQL rows in the CSV

Before you change a Databricks table

  1. Filter the script's CSV for the table, and review every NativeSql row by hand.
  2. Run the lineage query for that table over a window longer than your slowest refresh.
  3. Tell the owners of every model on either list, with the date of the change.
  4. For renames, keep the old name working for a while: a view with the old name over the new table gives models time to move.
  5. After the change, refresh the affected models, compare row counts and key totals, and run the script again.

Longer term, point Power BI at views rather than tables. A view per model, or per subject area, is a contract: the table underneath can change while the view keeps its shape. If you want the dependency to show inside Unity Catalog's lineage view as well, register each model through the external lineage API.

When to ask for help

With a few dozen models this is an afternoon. With hundreds, owned by many teams, the map needs refreshing every week and the gaps need chasing. That is the Estate Audit we run: the full map of models to tables, what breaks for each planned change, and which models are no longer used. Start with the free one-page summary from the script.

Sources: Power BI lineage view, Power BI impact analysis, Databricks lineage system tables, Databricks query history system table, Unity Catalog external lineage, Microsoft Purview lineage for Power BI, DirectQuery in Power BI. Related: Find the reports still on the old Databricks driver.

Free review: send us your one-page summary

Run the script with -SummaryHtml and attach the page. It holds counts only, no names or hosts. Within a working day we reply with what we would check first before your next Databricks change. No charge, no call needed.