Power BI Lesson 24 – Replace Errors | Dataplexa
Power Query · Lesson 24

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.

Step-level error — the whole query breaks
The entire Applied Step fails. The preview shows a full-screen error message instead of a data grid. Every step below it also fails. Nothing loads into the model.
Expression.Error
The column 'Revenue' of the table wasn't found.
Fix: correct the step — update column reference, fix file path, align privacy levels
Value-level error — one cell breaks, rest loads
Only specific cells in specific rows contain errors. The rest of the column — and the rest of the table — loads fine. The error cell shows "Error" in red in the preview grid.
DataFormat.Error
We couldn't convert the value "N/A" to type Number.
Fix: replace, remove, or use try…otherwise on that column

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}
    )
)
When to use each replacement value
Replace with null
Best for numeric columns — null is excluded from SUM, AVERAGE, and MIN/MAX automatically. The row stays in the table but does not corrupt aggregations. Use when "I don't know this value" is the correct interpretation.
Replace with 0
Use only when 0 is genuinely the correct value — for example, a Discount column where an error means "no discount was applied." Never use 0 as a Revenue fallback — it pulls down averages silently.
Replace with text
For text columns, replace errors with "Unknown" or "Unclassified" — a visible, searchable placeholder that shows up in slicers and filters, making it obvious which rows need attention.

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"
try…otherwise applied to Revenue column
Raw Revenue (text) try Number.From otherwise null HasError flag
"1200"1200Valid
"N/A"nullInvalid
"430"430Valid
"$1,540"nullInvalid
"850"850Valid
No red error cells — try…otherwise catches failures silently · HasError column lets you filter or report invalid rows separately

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.

Column Quality bar
View tab → Column Quality. The red segment of the bar shows the error percentage instantly. Click any error cell to see the exact error message below the grid.
Keep Errors to inspect
Home → Keep Rows → Keep Errors. Temporarily filters the table to show only error rows — useful for understanding the pattern of what values are causing errors before deciding how to handle them.
Count errors with M
Use Table.SelectRows with try to count error rows before removing them — gives you a numeric count to monitor over refreshes and alert on if it suddenly spikes.
// 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"
Defensive column handling — what each pattern prevents
Table.HasColumns check
Prevents "column wasn't found" step errors when the source file removes or renames a column between refreshes. The query continues with a null-filled column rather than breaking entirely.
List.Accumulate required cols
Guarantees the downstream steps always find the columns they reference. Any missing column is silently added with nulls — the model loads, visuals show blank for missing data, and you can investigate why the column disappeared.
Safe Rename
Handles sources that were already partially cleaned — if the file sometimes arrives with "rev" and sometimes with "Revenue", the rename only runs when needed. Prevents "column wasn't found" on the rename step.

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.