Power BI Course
Replacing Errors and Handling Exceptions
Source data is unpredictable. Even after building a thorough cleaning pipeline, a new month's file will arrive with a value format you did not anticipate, a column that briefly disappeared, or a calculation that divides by zero for a specific customer segment. This lesson covers the defensive programming techniques that make Power Query queries resilient — detecting errors before they break a step, using try…otherwise to recover gracefully, and building conditional logic that handles exceptions without stopping the entire query.
The Difference Between Step Errors and Value Errors
Power Query has two distinct error levels and they require different handling strategies. Confusing the two is the main reason beginners end up with queries that either break completely or silently produce wrong values.
Replace Error Values — The UI Approach
For the simplest cases — when you know a column will occasionally contain errors and you want to substitute a fixed fallback value — Replace Error Values is the fastest fix. It is available on both a per-column basis and across all columns at once.
// Replace errors in a single column — via UI or M
// Transform tab → Replace Errors → enter replacement value
// M generated for replacing Revenue errors with null:
#"Replaced Errors" = Table.ReplaceErrorValues(
#"Changed Type",
{{"Revenue", null}}
)
// Replace errors in multiple columns in one step:
#"Replaced Errors" = Table.ReplaceErrorValues(
#"Changed Type",
{
{"Revenue", null}, // Revenue errors → null
{"Quantity", 0}, // Quantity errors → 0
{"Discount", 0.0} // Discount errors → 0.0
}
)
// Replace errors across ALL columns at once (no UI — M only):
#"Replaced All Errors" = Table.TransformColumns(
#"Changed Type",
List.Transform(
Table.ColumnNames(#"Changed Type"),
each {_, each try _ otherwise null}
)
)
try…otherwise — Conditional Error Handling in M
The try…otherwise expression is M's equivalent of try/catch in other programming languages. It attempts an expression and if that expression produces any error, returns a fallback value instead. Unlike Replace Error Values, which only works after a step has run, try…otherwise wraps the calculation itself — preventing the error from ever being generated.
// Basic syntax
try expression otherwise fallback_value
// Example 1 — Safe type conversion
// Instead of letting Changed Type produce errors:
// Add Column → Custom Column
= try Number.From([Revenue]) otherwise null
// If Revenue contains "N/A" → returns null (no error cell)
// If Revenue contains "1200" → returns 1200
// Example 2 — Safe division (avoid divide-by-zero errors)
= try [Revenue] / [Quantity] otherwise null
// If Quantity = 0 → returns null instead of error
// If Quantity > 0 → returns the division result
// Example 3 — Safe date parsing
= try Date.From([OrderDate]) otherwise null
// If OrderDate is a valid date string → returns a date
// If OrderDate is "TBD" or blank → returns null
// Example 4 — try with error inspection
// try returns a record with [HasError], [Value], [Error]
// Use this when you want to log the error message too
= try Number.From([Revenue])
// Returns: [HasError = false, Value = 1200, Error = null]
// or [HasError = true, Value = null, Error = [record]]
// Access just the value safely:
= (try Number.From([Revenue]))[Value]
// Access HasError to create a flag column:
= if (try Number.From([Revenue]))[HasError]
then "Invalid"
else "Valid"
| Raw Revenue (text) | try Number.From otherwise null | HasError flag |
|---|---|---|
| "1200" | 1200 | Valid |
| "N/A" | null | Invalid |
| "430" | 430 | Valid |
| "$1,540" | null | Invalid |
| "850" | 850 | Valid |
Detecting and Flagging Errors Before Handling Them
Before deciding whether to replace or remove errors, it is worth understanding how many there are and why they exist. A column with 2% errors is handled differently from one with 40% errors — the latter suggests a structural problem in the source, not just occasional bad data.
// Pattern: count error rows before removing them
// Add this as a separate diagnostic query (load disabled)
let
Source = Orders, // reference the cleaned Orders query
// Count rows where Revenue has an error
ErrorCount = Table.RowCount(
Table.SelectRows(
Source,
each (try [Revenue])[HasError] = true
)
),
// Count total rows
TotalRows = Table.RowCount(Source),
// Build a summary record
Summary = [
ErrorRows = ErrorCount,
TotalRows = TotalRows,
ErrorPct = Number.Round(ErrorCount / TotalRows * 100, 1)
]
in
Record.ToTable(Summary)
// This produces a small 3-row table:
// Name Value
// ErrorRows 12
// TotalRows 500
// ErrorPct 2.4
Handling Missing Columns Defensively
A query that references a column by name breaks immediately if that column is missing from the source. This is one of the most common causes of refresh failures — the source file was updated and a column was renamed or removed. A defensive pattern adds the column with a default value if it is missing, rather than erroring.
// Defensive column — add it with a default if missing from source
// Pattern: check if column exists, add it with null if not
#"Safe Discount" = if Table.HasColumns(#"Changed Type", "Discount")
then #"Changed Type"
else Table.AddColumn(#"Changed Type", "Discount", each null, type number),
// Pattern: ensure a set of required columns always exist
// Useful when sources merge or split columns inconsistently
RequiredColumns = {"OrderID", "Revenue", "Discount", "Status"},
#"With Required Cols" = List.Accumulate(
RequiredColumns,
#"Changed Type",
(state, colName) =>
if Table.HasColumns(state, colName)
then state
else Table.AddColumn(state, colName, each null)
),
// Pattern: rename a column only if the old name exists
// (handles sources where the column was already renamed)
#"Safe Rename" = if Table.HasColumns(#"Changed Type", "rev")
then Table.RenameColumns(#"Changed Type", {{"rev", "Revenue"}})
else #"Changed Type"
Replace Errors vs try…otherwise — When to Use Each
| Table.ReplaceErrorValues | try…otherwise in Custom Column | |
|---|---|---|
| How it works | Runs after a step — finds existing error cells and replaces their value. Errors must have already been generated. | Wraps the expression itself — the error is never generated. The fallback is returned in its place immediately. |
| Available in UI? | Yes — Transform tab → Replace Errors or right-click column | No — must be written in M via Add Column → Custom Column |
| Handles step errors? | No — only handles value-level errors inside cells | No — only handles value-level errors inside a Custom Column expression |
| Can inspect the error? | No — just replaces with the fixed value | Yes — try returns a record with HasError, Value, and Error fields. You can log the reason for each failure. |
| Best used for | Quick cleanup after a type change step. Simple cases where all errors in a column get the same fallback. | New calculated columns where division by zero, missing values, or unparseable formats are expected. When you need different fallbacks per row based on the error type. |
Teacher's Note: The most valuable habit you can build is treating every Custom Column that does arithmetic as a potential division-by-zero waiting to happen. The moment you write [Revenue] / [Orders], wrap it immediately: try [Revenue] / [Orders] otherwise null. It takes two seconds and prevents the entire query from showing error cells the first time a new region or product category has zero orders for a month. Defense costs almost nothing; fixing a broken report at 9am before a board meeting costs everything.
Practice
Practice 1 of 3
You write a Custom Column formula that divides Revenue by Quantity. Some rows have Quantity = 0. To prevent error cells in the result without removing those rows, you wrap the formula as: ___ [Revenue] / [Quantity] otherwise null.
Practice 2 of 3
To temporarily filter a table in the Power Query Editor to show only the rows that contain errors — useful for inspecting what values are causing them — you go to Home → Keep Rows → Keep ___.
Practice 3 of 3
To check whether a column named "Discount" exists in a table before referencing it — and add it with null values if it is missing — you use the M function Table.___(table, "Discount") as the condition.
Lesson Quiz
Quiz 1 of 3
You use Table.ReplaceErrorValues to replace all errors in a Margin % column with 0. A month later a colleague notices the average margin for the West region looks unusually low. What is the most likely cause?
Quiz 2 of 3
A query that ran perfectly for six months suddenly fails on refresh with "The column 'PromotionCode' of the table wasn't found." The source team says the column was removed from the export. What is the most robust fix that lets the report keep loading without that column?
Quiz 3 of 3
You use try Number.From([Revenue]) in a Custom Column without the "otherwise" clause. What does the column return for a row where Revenue = "N/A"?
Next up — Lesson 25 covers Data Formatting in Power Query — number formats, date formats, text padding, rounding functions, and how formatting decisions made here affect what DAX and visuals see downstream.