02 · Advanced Power Query (M Language Basics)¶
Every Power Query transformation is really generating code in the M language, visible via the Advanced Editor. This module reads and hand-edits M directly — custom columns, conditional logic, and merging two queries — instead of only clicking Ribbon buttons.
1. Worked dataset¶
Two tables, both converted to Excel Tables.
Orders (A1:C6):
| A | B | C | |
|---|---|---|---|
| 1 | OrderID | CustID | Amount |
| 2 | 1 | C1 | 500 |
| 3 | 2 | C2 | 300 |
| 4 | 3 | C1 | 800 |
| 5 | 4 | C3 | 200 |
| 6 | 5 | C2 | 450 |
Customers (A1:B4):
| A | B | |
|---|---|---|
| 1 | CustID | Name |
| 2 | C1 | Alan |
| 3 | C2 | Priya |
| 4 | C3 | Sam |
2. Viewing the M code¶
- Load
Ordersinto Power Query (Data → From Table/Range). View → Advanced Editor. The base query looks like: - Every UI action (a filter, a rename, a group) appends another line
inside the
let...inblock, bound to a name, and the finalinline names which step's output is returned.
3. Custom column with M¶
- Add Column → Custom Column, name
Tier, formula:if [Amount] >= 500 then "Large" else "Small" - This appends a step to the M code:
- Manual check row by row: 500→Large, 300→Small, 800→Large, 200→Small, 450→Small. Three Small, two Large.
4. Merging two queries¶
- Load
Customersas its own query too. In theOrdersquery, Home → Merge Queries. MatchOrders.CustIDtoCustomers.CustID, Join Kind Left Outer (keep every order, add customer info where it matches). - Underlying M:
Table.NestedJoin(Orders, {"CustID"}, Customers, {"CustID"}, "Customers", JoinKind.LeftOuter). Expand the newCustomerscolumn, keeping onlyName. - Manual check: OrderID 1 (CustID C1) → Alan; OrderID 2 (C2) → Priya; OrderID 3 (C1) → Alan; OrderID 4 (C3) → Sam; OrderID 5 (C2) → Priya. Every order row must now show the correct customer name.
5. Editing M by hand¶
- In the Advanced Editor, manually insert a step that trims
whitespace from the Name column defensively, even though this
dataset has none:
and update the final
inline toin #"Trimmed Name". - This demonstrates the general
Table.TransformColumnspattern:Table.TransformColumns(table, {{"ColumnName", function, type}})appliesfunctionto every value inColumnName.
Cheat sheet¶
| M function | Purpose |
|---|---|
Table.AddColumn(table,name,each expr) |
Add a computed column |
Table.NestedJoin(...) |
Merge two tables (the engine behind Merge Queries) |
Table.TransformColumns(table,{{col,fn,type}}) |
Apply a function to a column |
let ... in ... |
Every query's structure: named steps, final returned step |
How It Actually Works¶
M, the language behind every Power Query step, is a pure, lazily-evaluated
functional language — every step is actually a function call that takes
the previous step's table as its argument and returns a new table, and the
query as a whole is just one composed function (let ... in ...)
referencing each step by name. Because M is lazy, the engine doesn't
necessarily compute step 3's entire result before running step 4 — it can
analyze the whole chain and, wherever the source supports it, use query
folding: translating your point-and-click steps back into the source
system's native query language (a SQL WHERE/SELECT for a database
connection, for instance) so filtering and column selection happen at the
source rather than after pulling every row into Excel. This is why step
order matters for performance even though it doesn't change the result:
a Filter step placed before a custom column that can't be folded (like
one calling a non-foldable M function) still gets pushed to the source,
but a Filter placed after that unfoldable step forces all rows to be
pulled locally first, breaking the folding chain for every step after it.
Table.Buffer exists specifically to force eager evaluation at a chosen
point, deliberately trading folding/laziness for a predictable, single
materialization when a step is expensive and would otherwise be
re-evaluated multiple times downstream.
Exercise¶
Add a custom column AmountWithTax using M:
each [Amount] * 1.08. Manually verify row 1: 500*1.08=540, row 3:
800*1.08=864. Confirm the query's output matches both by hand.