Power BI · Chapter 2 · Concept lesson · 7 min read

Power Query records steps, not results

Power Query does not change your data. It records a recipe of steps that runs again from the source on every refresh, which is why the order of the steps decides the result.

The idea

When you click Transform → Trim in the Power Query Editor, Power Query does not edit your data. It writes a line in a recipe. The right-hand Applied Steps list is that recipe, top to bottom: a Source step, then everything you did.

The table you see in the editor is the recipe played up to the step you have selected. Select an earlier step and the table goes back to how it was. Nothing was lost, because nothing was ever changed. The source stays as it was, and the steps only describe what to do with it.

Figure 1

A query is a chain of steps replayed from the source

  1. Source customers table
  2. Changed Type signup_date becomes Date
  3. Trimmed Text city
  4. Capitalized Each Word city
  5. Removed Duplicates email
  6. Close & Apply result loads into the model

Step 1 of 5 Each step takes the table the previous step produced and returns a new one. Nothing is edited in place, and the source is never touched.

  1. Each step takes the table the previous step produced and returns a new one. Nothing is edited in place, and the source is never touched.
  2. You click **Transform → Data Type** and Power Query writes a step called Changed Type. The Applied Steps list on the right is the recipe.
  3. Clean-up steps stack on top: first Trimmed Text strips the stray spaces, then Capitalized Each Word fixes detroit.
  4. Removed Duplicates comes after the text clean-up. Two rows that differed only by a trailing space now match and one of them goes.
  5. Close & Apply loads the final table into the model. At the next refresh the whole chain runs again, top to bottom, on the fresh source.

Why it exists

Think of what a Monday morning looks like without it. Someone opens the exports, trims the same cities, fixes the same prices that came in as text and deletes the same blank rows by hand. Next Monday they do it again, from memory, and some steps get skipped.

A recorded recipe fixes that. You build the clean-up once. Every refresh replays it on the fresh source, in the same order, with no hands. And because every step has a name, a colleague can open the list and see exactly what happened to the numbers: a change nobody can see is the one nobody trusts.

How it works under the hood

Every step is a line of M

Each step is a line of code in a language called M. Open Home → Advanced Editor and the whole query is there, one step per line, each one using the name of the step above it:

let
    Source = Sql.Database("harborpine", "store"),
    customers = Source{[Schema="dbo", Item="customers"]}[Data],
    Trimmed = Table.TransformColumns(customers, {{"city", Text.Trim, type text}}),
    Capitalized = Table.TransformColumns(Trimmed, {{"city", Text.Proper, type text}})
in
    Capitalized

You never have to write M for the everyday jobs. The ribbon writes it for you. But reading it once makes the main rule obvious: each step is a function of the step before it.

The order is the logic

If a step depends on the previous result, the order changes the answer. Three common cases:

  • Clean text before you compare it. Trim and capitalise first, then remove duplicates or merge. Otherwise Tucson and Tucson do not match.

Figure 2

Why trim comes before the grouping: a city with a space is a different city

Source: customers[city] as it arrives
customer_idcitycharacters
16 detroit 10
32San Francisco 14
70 Tucson7
133 philadelphia 15
After Trimmed Text and Capitalized Each Word
customer_idcitycharacters
16Detroit7
32San Francisco13
70Tucson6
133Philadelphia12

Step 1 of 3 In the real customers table, 51 customers have a city with a leading or trailing space. The characters column counts what you cannot see.

  1. In the real customers table, 51 customers have a city with a leading or trailing space. The characters column counts what you cannot see.
  2. To a table, Tucson and Tucson are two different values. A slicer or a Group By would list both.
  3. After the two steps the spaces are gone (10 characters become 7 for Detroit) and every city starts with a capital.
  • Fill down before unpivot. The blank cells have to be filled while the groups are still in columns. Unpivot first and the nulls are copied into every new row.
  • Change the type before you filter on it. A filter "greater than 100" on a text column compares text, and "9" sorts after "100".

Figure 3

A month-per-column export becomes one row per fact

Source: Q1 2025 units, made for printing
categorysubcategoryJanFebMar
DecorCandles8056115
NULLMirrors10993120
After Fill Down on category
categorysubcategoryJanFebMar
DecorCandles8056115
DecorMirrors10993120
After Unpivot Columns on Jan, Feb, Mar
categorysubcategorymonthunits
DecorCandlesJan80
DecorCandlesFeb56
DecorCandlesMar115
DecorMirrorsJan109
DecorMirrorsFeb93
DecorMirrorsMar120

Step 1 of 4 The finance export: the category is written once per group and the months run across the top.

  1. The finance export: the category is written once per group and the months run across the top.
  2. The blank under Mirrors is a null. If the table is unpivoted now, the null travels into every new row.
  3. Fill Down copies Decor into the null. Every row now knows its own group.
  4. Unpivot Columns turns Jan, Feb and Mar into values of one month column. Two rows become six; one units column can now be summed.

If a late step breaks, click an earlier one in the list and look at the table at that point. That is the debugger.

Folding: the steps that never run in Power Query

When the source is a database like SQL Server, Power Query tries to translate your early steps into one SQL query and let the database do the work. This is query folding. Choose columns and Filter rows fold into SELECT ... WHERE ..., so only the columns and rows you kept travel to Power BI. Right-click a step and look at View Native Query: if it is enabled, the step folded.

Some steps cannot fold (a custom column that runs M code, for example). Once one does, every step after it runs inside Power Query on the rows already downloaded. So put the filters and column choices first, as the first level of this chapter does.

Types are steps too

Power Query adds a Changed Type step on its own right after Source, guessing from the first rows. Treat that guess as a draft. A price that arrived as text such as "31.99" cannot be summed until it is a decimal number, and a date column typed as text will not relate to the Date table. Check the small icon in each column header (ABC for text, 1.2 for decimal, a calendar for date) and set the type deliberately, as the second level of this chapter does.

Merge and append reuse the same idea

Merge Queries joins two queries on a key, like a SQL JOIN, and adds the other table as a column you expand. Append Queries stacks tables with the same columns on top of each other, as when the marketplace sends one export per month. Both are steps like any other, and both take another query as input. The queries you reference then show up in the Query Dependencies view (View → Query Dependencies), which is the map of where each table comes from.

Name the steps

A step called Changed Type2 tells a colleague nothing. Right-click a step, Rename, and call it what it does: "Trim city", "Keep completed orders". Hover a step and its note appears. Ten minutes of naming saves an hour the day somebody else has to explain the numbers.

Where it ends

Close & Apply loads the result into the model. From then on the query is a table in the model and refreshing the report replays the whole chain. If you change a step later, the next refresh rebuilds the table, so measures keep working as long as the column names they use still exist.

Common misconceptions

"Power Query changes the original data"

It reads the source and never writes back. You can delete every column in the editor and the Excel file or the SQL table is exactly as it was. The only thing that changes is the recipe and what is loaded into the model.

"The order of the steps does not matter, it is just a list of cleanups"

It is a pipeline, not a checklist. Each step receives the output of the one before. Trim after a duplicate removal, unpivot before a fill down or filter before a type change, and the result is different even though the same steps were applied.

"I cleaned it, so it stays clean"

Only while the clean-up is a step. If you fix a value by typing over it in an Excel export, the next export brings the old mess back. A step that replaces or trims reapplies itself on every refresh, including the data that does not exist yet.

Check yourself

One question. Answering it ticks this lesson off on the map; it never locks a level.

A query has these steps in order: Source, Removed Duplicates on email, Trimmed Text on email. Two customers are the same person, but one email has a trailing space. What happens?

Pick what may run on this site. You can change this any time from "Cookie settings" at the bottom of every page.