User-defined aggregations are a powerful - yet often overlooked - tool for optimizing query performance, especially when working with large DirectQuery semantic models. By configuring specialized aggregation tables within your data model, you enable Power BI to automatically serve commonly requested summaries from in-memory cache rather than querying the backend source each time. This not only delivers faster and more responsive reports but also reduces the workload on source systems, making your analytics solutions more scalable and efficient.
In this quickstart, you’ll learn how to set up User-defined aggregations and see first-hand how they can significantly boost the performance of your Power BI semantic models.
Before you begin, ensure you have the following:
- Databricks account, access to a Databricks workspace, Unity Catalog, and Databricks SQL Warehouse
- Power BI Desktop, latest version is highly recommended
-
Create a catalog and a schema in Databricks Unity Catalog.
CREATE CATALOG IF NOT EXISTS powerbiquickstarts; USE CATALOG powerbiquickstarts; CREATE SCHEMA IF NOT EXISTS tpch; USE SCHEMA tpch;
-
Create test tables in the catalog by replicating tables from
samplescatalog.CREATE OR REPLACE TABLE nation AS SELECT * FROM samples.tpch.nation; CREATE OR REPLACE TABLE customer AS SELECT * FROM samples.tpch.customer; CREATE OR REPLACE TABLE orders AS SELECT * FROM samples.tpch.orders; CREATE OR REPLACE TABLE lineitem AS SELECT * FROM samples.tpch.lineitem; CREATE OR REPLACE TABLE lineitem_by_nation_agg AS SELECT `n_nationkey` , min(`l_shipdate`) as `EarliestShipdate` , sum(`l_discount`) as `SumofDiscount` , sum(`l_quantity`) as `SumOfQuantity` FROM `samples`.`tpch`.`lineitem` INNER JOIN `samples`.`tpch`.`orders` on `l_orderkey` = `o_orderkey` INNER JOIN `samples`.`tpch`.`customer` on `o_custkey` = `c_custkey` INNER JOIN `samples`.`tpch`.`nation` on `c_nationkey` = `n_nationkey` GROUP BY ALL; CREATE OR REPLACE VIEW v_nation AS SELECT *, now() as currenttime FROM nation;
-
Open Power BI Desktop → "Home" → "Get Data" → "More...".
-
Search for Databricks and select Azure Databricks (or Databricks when using Databricks on AWS or GCP).
-
Enter the following values:
- Server Hostname: Enter the Server hostname value from Databricks SQL Warehouse connection details tab.
- HTTP Path: Enter the HTTP path value from Databricks SQL Warehouse connection details tab.
Tip
We recommend parameterizing your connections. This really helps ease out the Power BI development and administration experience as you can easily switch between different environments, i.e., Databricks Workspaces and SQL Warehouses. For details on how to parameterize your connection string, you can refer to Connection Parameters article.
-
Connect to
powerbiquickstartscatalog,tpchschema. -
Add tables to the semantic model as follows.
-
v_nation- DirectQuery storage mode. Dimension table containing nation name and details. Rename it tonationin the Power BI semantic model. -
customer- DirectQuery storage mode. Dimension table containing customer information and connected to nation dimension table using nationkey. -
orders- DirectQuery storage mode. Fact table containing orders information and connected to customer dimension using customerkey. -
lineitem- DirectQuery storage mode. Fact table containing details like order shipment date, discount price etc. -
nation_agg- DirectQuery storage mode. Replica ofv_nationtable and used for aggregate table report. -
customer_agg- DirectQuery storage mode. Replica ofcustomertable and used for aggregate table report. -
orders_agg- DirectQuery storage mode. Replica oforderstable and used for aggregate table report. -
lineitem_agg- DirectQuery storage mode. Replica oflineitemtable and used for aggregate table report. -
lineitem_by_nation_agg- DirectQuery storage mode. Aggregatedlineitemdata.
-
-
Create table relationships as follows.
nation→customer→orders→lineitemnation_agg→customer_agg→orders_agg→lineitem_aggnation_agg→lineitem_by_nation_agg
Important
Here we use v_nation view, not nation table. The view uses now() function that prevents QRC (Query Result Caching). Therefore, for every query execution we will have non-cached query profile with execution metrics available for analysis.
Important
The relationship between nation_agg and lineitem_by_nation_agg must be One to many (1:*) with Single cross-filter direction.
The resulting data model should look like on the screenshot below.
Next, we will analyze the performance of a test report using pure DirectQuery mode and User-defined Aggregation.
-
Create a new report page. Add a Table visual. Add columns to the Table visual as follows.
n_nationfromnationtable- Sum of
l_discountfromlineitemtable - Sum of
l_quantityfromlineitemtable - Earliest
l_shipdatefromlineitemtable
-
Open Optimize → Performance Analyzer → Start Recording → Refresh visuals.
-
Perfomance Analyzer tab will display the Table visual and a DAX query. Click on Copy Query. The DAX query should look similar to Sample DAX Query script. Below is the screenshot of DirectQuery report page.
-
To compare the performance between pure DirectQuery and User-defined Aggregations, it is important to get objective and precise query execution times.
-
Open DAX Studio, click Connect, choose local Power BI mode, click Connect, and click Server Timings.
-
Open the Sample_DAX_Query.dax query or paste DAX-query that was previously copied in Power BI Desktop. Click Run. As shown in screenshot below, the query takes 2.167 sec.
-
You can also find the SQL query execution time by looking at Databricks Query History. As shown below, the query took 2.053 sec and read ~38M rows.
The SQL query looks as follows. Power BI built a SQL query joining
nationdimension table withordersandlineitemfact tables.select ... from ( select ... from ( select ... from ( select ... from ( select ... from `powerbiquickstarts`.`tpch`.`lineitem` as `OTBL` inner join `powerbiquickstarts`.`tpch`.`orders` as `ITBL` on (`OTBL`.`l_orderkey` = `ITBL`.`o_orderkey`) ) as `OTBL` inner join `powerbiquickstarts`.`tpch`.`customer` as `ITBL` on (`OTBL`.`o_custkey` = `ITBL`.`c_custkey`) ) as `OTBL` inner join `powerbiquickstarts`.`tpch`.`nation` as `ITBL` on (`OTBL`.`c_nationkey` = `ITBL`.`n_nationkey`) ) as `ITBL` group by `n_name` ) as `ITBL` where ...
-
Switch to Power BI Desktop → Model view.
-
Right Click lineitem_by_nation_agg → Manage aggregations.
-
Configure aggregation table as shown below on the screenshot.
Aggregation column Summarization Detail table Detail column n_nationkeyGroupBy nationn_nationkeyEarliestShipdateMin lineitem_aggl_shipdateSumofDiscountSum lineitem_aggl_discountSumOfQuantitySum lineitem_aggl_quantity
-
Create a new report page. Add a Table visual. Add columns to the Table visual as follows.
n_nationfromnation_aggtable- Sum of
l_discountfromlineitem_aggtable - Sum of
l_quantityfromlineitem_aggtable - Earliest
l_shipdatefromlineitem_aggtable
-
Open Optimize → Performance Analyzer → Start Recording → Refresh visuals.
-
Perfomance Analyzer tab will display the Table visual and a DAX query. Click on Copy Query. The DAX query should look similar to Sample_DAX_Query_Using_Aggregations script. Below is the screenshot of User-defined Aggregation report page.
-
Open DAX Studio, click Connect, choose local Power BI mode, click Connect, and click Server Timings.
-
Open the Sample_DAX_Query_Using_Aggregations.dax query or paste DAX-query that was previously copied in Power BI Desktop. Click Run.
-
As shown in screenshot below the query takes 1.077 sec.
Also, as shown in the screenshot, the first row under RewriteAttempted shows MatchFound, i.e., Power BI was able to find the aggregate table for this query. Hence, during the query execution as shown in the screenshot the values are fetched from
lineitem_by_nation_agginstead oflineitem_aggfact table. -
You can also find the query execution time by looking at Databricks Query History. As shown below the query took ~0.994 sec and read only 50 rows (instead of ~38M rows).
The SQL query looks as follows. Power BI built a SQL query joining
nationdimension table with aggregated tablelineitem_by_nation_aggwhich is much smaller thanordersandlineitem, hence the query is much more efficient.select ... from ( select ... from ( select ... from `powerbiquickstarts`.`tpch`.`lineitem_by_nation_agg` as `OTBL` inner join `powerbiquickstarts`.`tpch`.`nation` as `ITBL` on (`OTBL`.`n_nationkey` = `ITBL`.`n_nationkey`) ) as `ITBL` group by `n_name` ) as `ITBL` where ...
Thus, by utilizing User-defined aggregation we achieved much higher performance and decreased the amount of I/O operations.
Tip
If you observe similar performance for both non-aggregated and aggregated tables, this may be due to results being served from Query Result Cache. To mitigate this, you may recreate test tables after configuring the report by executing SQL-statements mentioned above.
User-defined aggregations in Power BI provide significant performance benefits, especially for large semantic models utilizing DirectQuery. By defining specialized aggregation tables, Power BI can automatically redirect queries for summary information to these pre-aggregated tables, allowing frequently needed results to be served from a much smaller and efficient in-memory cache instead of the full detail tables in the backend. This results in much faster and more responsive reporting, drastically reducing query times and the number of rows scanned during execution - for example, dropping from tens of millions of rows to just a few dozen in test scenarios. This approach not only speeds up report visuals but also decreases the workload on source systems like Databricks SQL, leading to increased scalability, reduced resource consumption, and better overall efficiency for enterprise analytics solutions. User-defined aggregations are powerful for enabling high performance and responsive analytics on vast datasets without compromising detail or accuracy.
A Power BI template User-defined Aggregations.pbit, as well as supporting scripts, are present in this folder to demonstrate the usage of User-defined Aggregations outlined above. To use the template, simply enter your Databricks SQL Warehouse's ServerHostname and HttpPath, along with the Catalog and Schema names that correspond to the environment set up in the instructions above.
