3.4 Query Folding and Load Performance
Key Takeaways
- Query folding pushes Power Query transformations back to the data source as native queries (SQL), dramatically improving performance.
- Folding is supported by relational databases (SQL Server, Azure SQL, Oracle) but NOT by flat files (CSV, Excel) or web sources.
- Steps that break folding include adding custom columns with M functions, using complex merges, and Type changes after non-foldable steps.
- The 'View Native Query' option (right-click a step) shows the generated SQL — if grayed out, folding has stopped.
- For best performance, keep foldable steps at the top of the query and non-foldable steps at the bottom.
Query Folding and Load Performance
Quick Answer: Query folding translates Power Query steps into native data source queries (like SQL), so the source does the heavy lifting instead of Power BI. Check folding status by right-clicking a step — if "View Native Query" is available, folding is active. Steps like Filter Rows and Remove Columns fold well; Custom Columns and complex M functions break folding.
What is Query Folding?
Query folding is the mechanism by which Power Query translates transformation steps into native queries that are executed by the data source. Instead of downloading all data and transforming it locally, the data source performs the work.
Without Query Folding
Data Source → Download ALL rows → Power BI filters locally → Load result
With Query Folding
Data Source (executes WHERE, GROUP BY, etc.) → Returns only needed data → Load result
The performance difference can be dramatic — especially with large databases.
Checking Folding Status
Right-click any step in the Applied Steps pane:
| Menu Option | Meaning |
|---|---|
| View Native Query (available) | Step is folded — you can see the generated SQL |
| View Native Query (grayed out) | Folding has stopped at or before this step |
| Step folding indicator (icon) | Some versions show a database icon for folded steps |
Steps That Support Folding
These operations are typically translated to native SQL:
| Operation | SQL Equivalent |
|---|---|
| Filter Rows | WHERE clause |
| Remove Columns | SELECT specific columns |
| Sort Rows | ORDER BY |
| Group By | GROUP BY with aggregates |
| Remove Duplicates | DISTINCT |
| Rename Columns | Column aliases |
| Change Type | CAST/CONVERT |
| Top N Rows | TOP / LIMIT |
| Merge Queries (same source) | JOIN |
Steps That Break Folding
Once folding breaks at a step, ALL subsequent steps are also non-foldable:
| Operation | Why It Breaks Folding |
|---|---|
| Add Custom Column (M functions) | M functions have no SQL equivalent |
| Merge Queries (different sources) | Cannot cross-source join in SQL |
| Add Index Column | ROW_NUMBER not supported in all contexts |
| Pivot/Unpivot | Complex operations without direct SQL mapping |
| Complex M expressions | Text.Combine, List.Transform, etc. |
| Table.Buffer | Forces data into memory |
Optimizing for Query Folding
Rule 1: Foldable Steps First
Put foldable transformations (filter, remove columns, type changes) before non-foldable ones:
Bad order (folding breaks early):
- Add Custom Column (breaks folding)
- Filter Rows (not folded — runs locally on all data)
- Remove Columns (not folded)
Good order (maximizes folding):
- Filter Rows (folded — source returns less data)
- Remove Columns (folded — source returns fewer columns)
- Add Custom Column (runs locally, but on smaller dataset)
Rule 2: Filter Early
Reduce the data volume as early as possible:
// Apply filters that fold to the source
Table.SelectRows(Source, each [Year] >= 2024 and [Status] <> "Cancelled")
Rule 3: Select Only Needed Columns
Remove unnecessary columns early to reduce data transfer:
// Remove columns you don't need
Table.RemoveColumns(Source, {"InternalNotes", "TempFlag", "Debug"})
Rule 4: Use Native Query When Needed
For maximum control, write a native SQL query directly:
let
Source = Sql.Database("server", "database",
[Query="SELECT OrderID, CustomerID, Amount FROM Sales WHERE Year >= 2024"])
in
Source
Warning: Native queries disable query folding for subsequent steps because Power BI treats the result as a flat table.
Additional Load Performance Tips
Incremental Refresh
For large datasets, configure incremental refresh to only refresh new or changed data:
- Create RangeStart and RangeEnd parameters (Date/Time type)
- Filter your data query using these parameters
- Configure the incremental refresh policy in Power BI Desktop
- Publish to the Service where it automatically manages partitions
Aggregation Tables
Pre-aggregate data in Power Query for summary-level queries:
- Create a reference query from the detail table
- Apply Group By to aggregate to the needed level
- Load both detail and aggregate tables
- Configure aggregations in the model to route queries automatically
Disable Auto Date/Time
Power BI creates hidden date tables for each date column by default, increasing model size:
File → Options → Data Load → Uncheck "Auto Date/Time for new files"
Disable Background Data
Turn off background data loading during development:
File → Options → Data Load → Uncheck "Allow data preview to download in the background"
On the Exam
The PL-300 frequently tests:
- Understanding what query folding is and why it matters
- Identifying which steps support or break folding
- Optimizing step order to maximize folding
- Knowing how to check folding status (View Native Query)
- Configuring incremental refresh parameters
How can you verify that a Power Query step is being folded to the data source?
Your Power Query has these steps in order: (1) Connect to SQL Server, (2) Add Custom Column using M function, (3) Filter Rows, (4) Remove Columns. Which steps are likely folded?
Which of the following Power Query transformations is MOST LIKELY to support query folding when connected to a SQL Server source?