skip to content

When should transformation logic live in Power Query rather than upstream SQL or DAX?

level: principalimportance: should knowfreq 33%

answer

  1. ask how many consumers need it
  2. some sources only a connector can reach
  3. filters after load can only be DAX
  4. a folded step is already SQL at the source
  5. code inside a binary file is code nobody reviews

basics

~20 s

Keep in Power Query only shaping that is local to one model or reachable only through a connector — folders of files, APIs, unpivoting. Shared, expensive or auditable logic belongs upstream in the warehouse; logic that must react to user filters belongs in DAX.

solid answer

~50 s

Three placements, three different properties. **Upstream (warehouse views, dbt models)** gives version control, tests, one definition every consumer inherits, and the engine best equipped for heavy work — that is the default for anything more than one report needs. **Power Query** earns its place for shaping only this model needs, and for sources nothing upstream can reach: folders of files, paginated web APIs, an unpivot that never had a home in the warehouse. Its weakness is governance — the M lives inside a `.pbix` binary with no review, no tests and no reuse unless you publish it as a dataflow. **DAX** is for logic that must be recomputed against whatever the user filtered; anything static and row-by-row is cheaper as a source column, since imported columns compress better than calculated ones. A useful check: when a Power Query step folds, it *is* SQL running at the source — so the argument is about where the code is governed, not where the compute happens.

code

text · 7 lines
text
needed by >1 consumer?          -> upstream (view / warehouse model)
expensive join or dedup?        -> upstream
source only a connector reaches -> Power Query
shaping only this model needs?  -> Power Query
same shaping in 2+ reports?     -> dataflow, not copy-paste
must react to user slicers?     -> DAX measure
static row-level value?         -> source column, not calculated column

go deeper

for a junior

Know the three places logic can live — the source, Power Query, DAX — and that a measure is for values that must respond to what the user filtered.

for a middle

Explain why an imported source column usually beats a calculated column, and why a folded Power Query step and a view clause are the same compute in different places.

for a senior

Diagnose the drift: the same rule copied into several report files, refreshes slowed by shaping that stopped folding, and prototypes that quietly became production definitions.

for a principal

Own the placement policy across teams — what must be upstream and versioned, where dataflows are the shared middle, and how to stop business rules accumulating inside binary report files.

## Three places, and what each one actually gives you A Power BI model has three natural homes for logic, and the interview question is not "which is fastest" but "which properties do you need". **Upstream — a database view, a warehouse table, a dbt model.** Logic here is text in source control: reviewable, testable, diffable. Every consumer inherits it — this report, next quarter's report, the notebook the data scientist writes, the extract the finance team pulls. It runs in the engine designed for large-scale set processing. And it is fixed once for everyone when it is wrong. **Power Query.** Logic here is expressed in M as Applied Steps inside the report file. It is fast to write, visible while you build, and reaches sources nothing upstream can: a SharePoint folder of spreadsheets, a paginated REST API, a file a supplier emails. Its cost is governance — the code lives inside a binary artifact, is not reviewed, is not tested, and is copied rather than shared when a second report needs it. Publishing it as a dataflow converts it into a shared asset, which is the middle option teams reach for when the same shaping is needed twice. **DAX.** Logic here is evaluated at query time against the filters the user applied. That is its unique property and its only real justification: a measure must respond to the slicers on the page. Static row-level shaping done as a calculated column is the anti-pattern — a calculated column is materialised in the model and generally compresses less well than the same column imported from the source, so it costs memory to reproduce what the source could have handed you. ## The heuristics that actually decide it **Does more than one consumer need it?** If yes, upstream. A rule duplicated in three report files will diverge; the only question is when. This is the single strongest signal, and it outranks convenience every time. **Is it expensive?** Heavy joins, deduplication over large history, complex window logic — push it to the engine that scales, and let Power Query select from the result. This also protects refresh windows, because a folded select from a prepared view is dramatically cheaper than assembling the same thing step by step. **Is the source reachable only by a connector?** Files in a folder, an API, a spreadsheet nobody will ever load into the warehouse: Power Query is the only tool with hands on it, so shaping there is correct — not a compromise. **Must it change with what the user filtered?** Then it must be DAX. No amount of upstream preparation can precompute a value that depends on a selection made after the model was loaded. **Would you want a code review on it?** If the answer is yes — the revenue rule, the customer-active definition, anything a regulator or a CFO might ask about — that logic should not be sitting in a step list inside a binary file. ## Where teams actually go wrong The usual failure is drift, not any single wrong choice. A report ships with three cleanup steps in Power Query because it was quicker. A second report needs the same cleanup and copies the steps. Six months later there are eleven `.pbix` files with subtly different versions of the same rule and no way to diff them. Nobody made a bad decision; nobody made a decision at all. The opposite failure exists too: pushing every report-local convenience upstream, which turns the warehouse into a graveyard of single-consumer views nobody dares drop. "Only this model needs it" is a legitimate reason to keep something in Power Query, and it should be recorded as a decision rather than a default. ## The folding argument, which is often misread When a Power Query step folds, the source executes it as SQL. So a folded filter and a `WHERE` clause in a view are the *same compute in the same engine*. The choice between them is therefore not about performance at all — it is about where the code lives, who can see it, and who inherits the fix. Once a step stops folding, the calculus changes sharply: now Power Query is doing work locally that the source could have done, and the performance argument for moving it upstream becomes concrete as well as organisational. ## What to say when asked State the default — shared logic upstream, model-local shaping in Power Query, filter-dependent logic in DAX — then name the exceptions honestly: connector-only sources, prototypes that will be promoted once the definition stabilises, and the dataflow middle ground for logic shared by several reports but not owned by the data platform team. Add the governance point most candidates omit: the reason to move logic out of report files is rarely speed, it is that a report file is not a place where an organisation can review, test or version its business rules.

  • If a folded Power Query step runs as SQL at the source anyway, why move it into a view?
    Because the compute is identical and the governance is not. A view is text in source control: reviewable, testable, diffable, and inherited by every consumer including ones that never open Power BI. The same logic inside a report file is unreviewed, uncopyable except by hand, and invisible to anyone who does not open the .pbix.
  • When is a calculated column the wrong way to add a value that DAX could compute?
    Whenever the value does not depend on user filters. A calculated column is materialised in the model and generally compresses less well than the same column imported from the source, so it spends memory reproducing something the source could have supplied. Reserve DAX for logic that must be recomputed against the filters applied at query time.
  • What does a dataflow give you that copying Power Query steps between reports does not?
    One definition with one owner. The M lives in a shared artifact several models consume rather than in each report file, so a fix propagates instead of needing eleven edits, and the shaping becomes visible to people who never open a report. It is the pragmatic middle when logic is shared across reports but the data platform team does not own it.
  • How do you decide whether a prototype's Power Query logic should be promoted upstream?
    Promote when the definition has stopped moving and a second consumer appears — those are the two signals that the cost of duplication has begun. Until then, iterating in Power Query is legitimately faster. The failure mode is never deciding: logic that quietly stabilises inside report files and is then copied is how organisations end up with several versions of one rule.

saying these in an interview costs you the question

  • Puts every transformation in Power Query because it is quickest
  • Uses calculated columns for static values the source could supply
  • Claims upstream is always faster without mentioning folding
  • Copies the same M steps into several report files
  • Treats DAX as a general-purpose ETL language

context