Power query обработка ошибок

Using TRY..OTHERWISE in Power Query Replicates Excel's IFERROR So You Can Trap and Manage Errors In Your Queries.

In Excel we can use IFERROR to if our calculation results in an error, and we can then tell Excel to produce a different result, instead of the error.

Power Query doesn’t have IFERROR but it does have a way of checking for errors and replacing that error with a default answer, it’s called try otherwise

In this post I’ll show you how to use try otherwise to handle errors when loading data, how to handle errors in your transformations and how to handle errors when your query can’t locate a data source.

Watch the Video

Subscribe YouTube

Download Sample Excel Workbook

Enter your email address below to download the sample workbook.

By submitting your email address you agree that we can email you our Excel newsletter.

First up, let’s load data from this table.

sample data

I’ve already generated a couple of errors in this table, and of course I can obviously see them and I could fix them before loading into Power Query.

But when using Power Query this isn’t always the situation. Your query will be loading data without knowing what it is so how would it handle these errors?

Let’s load the data into Power Query and call it Errors from Sheet

errors in sheet

Straight away you can see the errors in the column.

Now of course you could use Remove Errors but that would remove the rows with the errors and that’s not what I want.

remove errors

Or I could use Replace Errors, but this doesn’t give me any idea what the cause of the error is.

replace errors

I want to see what caused the error and to do this I’ll add a Custom Column and use try [End]

try end

This creates a new column with a Record in each row

column of records

In this record are two fields. HasError states whether or not there’s an error in the [End] column

error record

If there is an Error then the 2nd field is another record containing information about that error

If there isn’t an error, then the 2nd field is the value from the [End] column

record with no error

If I expand the new column I get 3 new columns containing the HasError value which is boolean, and either an Error or a Value

expanding record column

Checking what’s in the Error Records, you can see the Reason for the error, DataFormat.Error, this is from Power Query

examining error record

There’s the Message, which is the error from the Excel sheet, and some errors give extra Detail, but not in this case.

If I expand this Error column I can see all of these fields.

expanded error column

I’ve ended up with a lot of extra columns here and it’s a bit messy so let’s tidy it up. In fact I’ll duplicate the query and show you another way to get the same information in a neater way

The new query is called Errors from Sheet (Compact) and I’ve deleted all steps except the first two.

compact query

What I want to do is , check for an error in the Try_End column, and if there is one I want to see the error message from Excel.

If there isn’t an error I want the value from the [End] column.

I can do all of this in a new column using an if then else

Add a new Custom Column called Error or Value and enter this code

try if then else

What this is saying is:

  • If the boolean value [HasError] in the [Try_End] column is true then
  • return the [Message] in the [Error] record of the [Try_End] column
  • else return the [Value] from the [Try_End] column

With that written I can remove both the End and Try_End columns so the final table looks like this

try if then else result

Checking for Errors and Replacing Them With Default Values

In this scenario I don’t care what the error is or what caused it, I just want to make sure my calculations don’t fail.

I duplicate the original query again, calling this one Error in Calculation, and remove every step except the Source step

I add a new Custom column called Result and what I’ll do here is divide [Start] by [End]

error dividing

this gives me an error as I know it will in rows 1 and 3

errors in calc column

so to avoid this, edit the step and use try .. otherwise

try otherwise to replace errors

now the errors are replaced with 0.

errors fixed

Errors Loading Data from A Data Source

I’ll create a new query and load from an Excel workbook

new query from workbook

Navigating to the file I want I load it

loading workbook

and loading this table

loading table from workboiok

table loaded to power query

I’m not going to do any transformations because I just want to show you how to deal with errors finding this source file.

I’ll open the Advanced Editor (Home -> Advanced Editor) and change the path, so that I know I’ll get an error. Here I change the drive letter to X.

I don’t have an X: drive so I know this will cause the workbook loading to fail.

change path to file

error loading file

So that’s what happens when the file can’t be found so let’s say I have a backup or alternate file that I want to load if my main file can’t be found.

Open the Advanced Editor again and then use try otherwise to specify the backup file’s location

try otherwise backup file

close the editor and now my backup file is loaded.

backup file loaded

IMPORTANT: You can read the official documentation that Microsoft has on this topic from the following link (url).

If you haven’t read the first two posts (Part 1 | Part 2) in this series yet, I welcome you to do so before reading this one.

I also recommend that you check out this post on Query Error Auditing so you get a better understanding of what types of errors you can find in Power BI / Power Query.

This is a post on how to use error handling, similar to an IFERROR in DAX and Excel, but for Power Query (using its M language).

How does Error handling works in Excel & DAX?

In Excel and in DAX we have the IFERROR function which works like this:

=IFERROR( value, value_if_error)

Taken directly from the official DAX documentation:

Evaluates an expression and returns a specified value if the expression returns an error; otherwise returns the value of the expression itself.

It’s a pretty simple and straightforward function in DAX and Excel, where you can enter your formula in the “value” parameter and then, if you get an error from it, you can define what should be the output value in the “value_if_error” parameter.

The whole idea is that you can “catch” an error and use a different value when it finds an error.

How does Error handling works in Power BI / Power Query?

In Power Query the code is a bit different. Let’s see it in action and then talk more about it.

Imagine that we have an Excel workbook with a table like this

image:

What we would like to create is a new column that should multiply the values from the [Price] and [Amount] columns to create a new Subtotal column.

One caveat, as you can probably see, is that this spreadsheet has some cells with errors on the [Price] column. In the event that we find an error on the Price column, we need to use the value from the [List Price] instead of the [Price] value.

The first thing that we need to do is import that table from Excel. If you’d like to follow along, you can download the workbook by clicking the button below:

Importing data from the Excel Workbook

I’ll be using Power BI Desktop for this, but you can use Excel as well.

The first thing that we need to do is select the Excel connector and connect to our file:

image

and once you get the “Navigator” window, you can select the table that reads “Sample”:

image

Notice how there’s a bunch of errors in that [Price] column just in the preview. Let’s hit the “Edit” button so we can go to the Power Query Editor Window.

Using error handling in Power BI /Power Query

Now that we have our data in the Power Query Editor window:

image what we want to do is create a Custom Column, so we simply go to the “Add Column” menu and hit on “Custom Column”.

In there, we try do create a simple column that will multiply the [Price] column by the [Amount] column:

image

and as you can see, our [Subtotal] column has some errors.

We know that in Excel and DAX you can use IFERROR, but what can you use in Power Query ?

For Power Query, we need to hit modify that Custom Column code (just click the gear icon next to the Added Custom step) and add the following pieces to it:

image

try [Price]*[Amount] otherwise [Amount]*[List Price]

We need to use the keywords “try” and “otherwise”. It’s pretty easy to read, but it just says to try and evaluate the expression ([Price] * [Amount]) and if that gives an error, use the expression defined after the otherwise statement.

The result of that will look like this:

image

pretty simple! almost as simple as the IFERROR function in DAX and Excel where intellisense does explain you a bit how to use that function, but in Power Query you need to understand how this works in order to use it. Is nowhere in the User Interface of Power Query, so you need to write this code manually.

Understanding Errors

The workbook sample that I’m using is fairly simple. I’ve had experiences where some users / customers absolutely need to know when a specific error is found from an Excel Workbook.

What happens with Power Query is that it just flags any errors found as “Error” but, what if you needed to know WHY it shows as an error?

Let’s go back to our initial load of the file. Remember that in most cases Power Query will automatically try to add a “Changed Type” step, so what if we remove that step?

image

Well, I removed the step and I’m still seeing the errors and that’s because the error wasn’t triggered by a data type conversion, but rather it’s a source error, meaning that the error comes directly from the Excel Workbook.

In Workbook with vast amounts of rows, it’s hard to tell if there are any errors at all and doing a “Replace Errors” will not tell us why those errors occurred. We NEED to know what is the error from the source because we want to handle each type of error differently.

Error Message and Error Reason

To figure out what’s the reason why there’s an error, we need to use the “try” statement again.

image

Note how I only use “try” and not the “otherwise” statement. This will give me a new column with record values. We can expand those records like this:

image

the most important field from those records it’s the “Error” field which can be either a null or a record value:

image

and after expanding that column and deleting some others that we don’t need, I end up with this:

image

I’ve highlighted the most important field after this whole process which is the “Message” which tells me exactly the reason why this is an error.

I can later use this to my advantage and target specific errors differently or get a report of ALL the errors found on a series of files that my department / group uses. This is extremely helpful if you’re trying to validate everything and make sure that we don’t have any errors at the source.

Don’t forget that these same principles work for both Step and cell Value level errors.

Your Power Query is skipping merrily along its mashup way. Then, bam! Something bad happens! Uh oh! What do you do when an error raises its ugly head? Or, for that matter, what if code you write detects an anomaly and you want to announce this fact in an informative manner?

Thankfully, M has error handling capabilities, allowing you to both raise and handle runtime errors. We’ll learn how to do both.

Important: If you’re familiar with the idea of an exception from other programming languages, Power Query’s error handling is different in at least one significant respect from what you may be familiar with.

Let’s get going!

Series Index

  • Introduction, Simple Expressions & let (part 1)
  • Functions: Defining (part 2)
  • Functions: Function Values, Passing, Returning, Defining Inline, Recursion (part 3)
  • Variables & Identifiers (part 4)
  • Paradigm (part 5)
  • Types—Intro & Text (Strings) (part 6)
  • Types—Numbers (part 7)
  • Types—The Temporal Family (part 8)
  • Types—Logical, Null, Binary (part 9)
  • Types—List, Record (part 10)
  • Tables—Syntax (part 11)
  • Tables—Table Think I (part 12)
  • Tables—Table Think II (part 13)
  • Control Structure (part 14)
  • Error Handling (part 15) [this post]
  • Type System I – Basics (part 16)
  • Type System II – Facets (part 17)
  • Type System III – Custom Types (part 18)
  • Type System IV – Ascription, Conformance and Equality’s “Strange” Behaviors (part 19)
  • Metadata (part 20)
  • Identifier Scope & Sections (part 21)
  • Identifier Scope II – Controlling the Global Environment, Closures (part 22)
  • Query Folding I (part 23)
  • Query Folding II (part 24)
  • Extending the Global Environment (part 25)
  • More to come!

Announcing an Error

In Power Query, each expression must produce something. Ideally, this is the expected value. However, there’s an alternative: an expression can raise an error, which is a special way of indicating that the expression could not produce a value.

The main way to raise an error is by using keyword error accompanied with a record describing the problem.

error [
  Reason = "Business Rule Violated", 
  Message = "Item codes must start with a letter", 
  Detail = "Non-conforming Item Code: 456"
]

In the error definition record, five fields are relevant: Reason, Message, Message.Format, Message.Parameters and Detail. Technically, all these fields are optional, and any extra fields included in the error definition record will be ignored.

Special behavior applies to field Reason and the Message* trio of fields:

  • Reason—If this field is missing, the error that’s raised will have its reason defaulted to “Expression.Error” (at least, this is true with the version of the mashup engine I’m using—technically, the language specification doesn’t mandate this defaulting).
  • Message* Fields—Two options are available for defining the error’s message: Directly specify a Message, or use Message.Format + Message.Parameters to define a structured error message (see New M Feature: Structured Error Messages for more details).

As an alternate to creating the error definition record by hand, helper method Error.Record can be used to build the record. The function’s first argument maps to field Reason. The second to either field Message or, if a list is passed as Error.Record‘s forth argument, to Message.Format. Arguments three and four map to Detail and Message.Parameters, respectively. Unlike the above build-your-own-record approach, Error.Record requires that you provide a Reason; its other arguments are optional.

error Error.Record("Business Rule Violated", "Item codes must start with a letter", "Non-conforming Item Code: 456")

It’s up to you as to whether you prefer to create error definition records using [...] syntax or with Error.Record. In either case, ultimately, a record is being created which you hand over to error when you’re ready for the error to be raised.

Both of the above examples produce an equivalent error:

Business Rule Violated: Item codes must start with a letter 
Details: Non-conforming Item Code: 456

Looking at the above screenshot, it’s easy to see how the three values that were provided map to the error messaging that’s displayed.

In lieu of a record, error also accepts a string. The resulting error will have its Message set to the provided string and its Reason set to “Expression.Error” (at least, that’s the default Reason with the mashup engine version I’m using—technically, the language specification doesn’t mandate this defaulting).

Expression.Error: help!

Ellipsis Shortcut

There’s also a shortcut operator for raising errors which comes in handy during development.

Let’s say you want to test a mashup that’s under development where you haven’t yet implemented every branch of each expression. Of course, since each branch must either return a value or raise an error, you can’t test run your query without putting something as a placeholder in those unimplemented branches, but what should you use?

When you encounter a situation like this, consider the ellipsis operator (...). When invoked, ... raises an error something like “Expression.Error: Not Implemented” or “Expression.Error: Value was not specified” (the exact wording depends on your mashup engine version).

Here’s a bit of code where the developer hasn’t yet implemented the if statement’s else branch so is using ... as a placeholder:

if Value then DoSomething() else ... // when Value evaluates to false, "..." is called, which raises the placeholder error

(Notice how keyword error is not used. The ellipsis operator both defines and raises the error. Very short, sweet and simple to use.)

Special Behavior

What exactly happens when an error is raised? What special behavior does raising an error entail that sets it apart from simply returning an ordinary value?

Let’s start with an expression:

When evaluated under normal circumstances, first GetValue() is executed. Then, the value it produces is passed into SomeFunction(). Lastly, SomeFunction()‘s result is returned as the expression’s output.

Heaven forbid, but suppose instead that GetValue() raises an error. Immediately, further execution of the expression stops. SomeFunction() is not called. Instead, GetValue()‘s error becomes the expression’s output: it is propagated (a.k.a. raised) to whomever or whatever invoked the expression.

What happens next depends on whether that whomever or whatever can hold a value: the error may be contained or may become the mashup’s top-level error. Only in case of the latter does the error cause the mashup as a whole to terminate.

Error Containment

If the error is encountered by an expression that defines something holding a value (like the expression for a record field, a table cell or a let variable), the error is contained by that something—its effects are limited to that something and any logic that attempts to access that something’s value.

Below, the effects of GetValue()‘s error are contained to the portion of the larger mashup affected by it. The error does not terminate the entire mashup; rather, the mashup completes successfully and returns a valid record. Only FieldB and FieldC are errored because they are the only “somethings” affected by the error.

let
  GetValue = () => error "Something bad happened!",
  DoSomething = (input) => input + 1,
  Result = [
    FieldA = 25,
    FieldB = DoSomething(GetValue),
    FieldC = FieldA + FieldB
  ]
in
  Result

[ FieldA = 25, FieldB = Error, FieldC = Error ]

This containment of errors brings with it another special behavior: When an error is contained, the error is saved into the something that contains it. Throughout the remainder of the mashup’s execution, any attempt to access that something’s value causes the saved error to be re-raised. When an access attempt occurs, the logic that originally caused the error is not re-evaluated to see if it now will produce a valid value; that logic is skipped and the previously saved error is simply re-raised.

Below, Data‘s GetDataFromWebService() is only evaluated once, even though Data itself is accessed twice. The second access attempt receives the error saved from the first access.

let
  Data = GetDataFromWebService() // raises an error
in
  { List.Sum(Data[Amount]), List.Max(Data[TransactionDate]) }

Top-Level Errors

When an error is encountered, if nothing contains it, the error is propagated from the mashup’s top-level expression (the mashup’s output clause) to the host environment as the mashup’s result. Execution of the mashup then stops.

This mashup’s top-level expression errors. Nothing is present to contain the error, so the mashup dies, outputting the error as its result:

let
  GetValue= () => error "Something bad happened!",
  SomeFunction = (input) => input + 1
in
  SomeFunction(GetValue())

The below mashup’s error is first contained in Result but then the top-level expression accesses Result which results in the error being re-raised to the top-level expression. Since nothing contains the error this time, it becomes the mashup’s output—like the preceding, the mashup dies with the error.

let
  GetValue= () => error "Something bad happened!",
  SomeFunction = (input) => input + 1,
  Result = SomeFunction(GetValue())
in
  Result

Containment vs. Exceptions

Power Query’s error containment behavior sets it apart from typical exception-based programming languages. In the world of exceptions, an error automatically propagates all the way to the host environment and so causes the program to die with an error—unless special handling is put in place. With M, an error is automatically contained, so long as something is present to contain it, allowing the mashup as a whole to complete successfully even if select data items could not be computed.

Error containment is a great behavior considering M’s target use case: processing data. Suppose the expression defining a table column value errors for one cell out of the entire table. In an exception-based world, this error might cause all processing to terminate. In M’s world, the error simply affects that single cell and any code that accesses that cell. Processing continues and the decision of whether the error is significant is left to whatever code consumes the cell’s value.

In fact, due to M’s laziness, if nothing ever attempts to use that cell’s value, its expression may not be evaluated, and so the error never raised. Why should the mashup engine waste effort computing something that will just be thrown away untouched?

let
  Data = #table({"Col1"}, {{"SomeValue"}, { error "bad" }})
in
  Table.RowCount(Data)

Above, row and column values are not needed to produce the requested output (the count of rows), so the second row’s error expression has no effect.

While error containment is a great default behavior, what if it doesn’t suit your needs? In particular, with tables, what if it’s important to differentiate between rows with errors and those without? Perhaps you’re not accessing row contents directly, so aren’t doing anything that would trigger error propagation, but still want to know which rows have an error somewhere in them and which do not. Table.SelectRowsWithErrors and Table.RemoveRowsWithErrors are likely just what you need.

let
  Data = #table({"Col1"}, {{"SomeValue"}, { error "bad" }})
in
  [
    RowsWithErrors = Table.RowCount(Table.SelectRowsWithErrors(Data)),
    RowsWithoutErrors = Table.RowCount(Table.RemoveRowsWithErrors(Data))
  ]

Handling Errors

With an understanding of raising errors tucked away, what do you do if you’re handed an error? Surely there’s a graceful way to handle it—some way to try to resolve it!

That’s it—that’s the keyword: try. try allows you to attempt to handle an error by taking remedial action.

try comes in three main variants:

// try otherwise
try ExpressionToTry otherwise FallbackExpression

// try catch
try ExpressionToTry catch (e) => FunctionBody
try ExpressionToTry catch () => FunctionBody

// plain try
try ExpressionToTry

try otherwise

The first version, try otherwise, tries to execute the expression to try. If that expression returns a value, try simply returns that value. If, instead, the expression errors, that error is ignored, the otherwise expression is evaluated and whatever that expression produces becomes the output of the try otherwise expression. In essence, if the first expression (the “to try” expression) errors, fallback to the second expression (the “otherwise” expression).

try Number.FromText(input) otherwise 0

If Number.FromText returns a value, then that value is returned from try. Instead, if Number.FromText raises an error, try handles that error, replacing it with the output produced by the otherwise expression (in this case, the value 0). So, if input can be parsed to a number, that number is returned; otherwise, a default value of 0 is returned.

Keep in mind that only the expression directly to the right of try will have its errors caught and replaced. If the otherwise expression returns an error, that error won’t be handled by the try coming before it. Of course, since the otherwise expression is itself just an expression, you could put a try inside that expression to handle errors raised from it.

try GetFromPrimary()
  otherwise try GetFromSecondary()
    otherwise "Having problems with both servers. Take the rest of the day off."

Try otherwise works well in a situations like text-to-number parsing but it can leave something to be desired in more complex scenarios. Why? The catch is that the otherwise is indiscriminate: it replaces any error by evaluating the fallback expression. Sometimes, the desired remedial action differs based on the specifics of the error encountered.

try catch

try catch allows us to handle this possibility. If the tried expression completes successfully (i.e. it returns a value), the value it produces is output. If, instead, the expression being tried raises an error, the catch function is invoked. This sounds very much like try otherwise, and it is—except for one very significant difference.

The catch function can be defined as accepting zero arguments or one argument. If a zero-argument function is used, then try catch is identical in behavior to try otherwise.

// both are equivalent in behavior
try Number.FromText(input) catch () => 0
try Number.FromText(input) otherwise 0

On the other hand, if the catch function is defined as accepting an argument, then when that function is invoked, it will be passed a record with details about the error that just occurred. This presents the possibility to dynamically adapt how the error is handled based on its specifics—a significant ability not possible with try otherwise.

let
  Source = 
    try GetDataFromPrimary()
    catch (e) =>
      // if the error is because primary is unreachable, fall back to secondary
      if e[Reason] = "External Source Error" and e[Message] = "Server is unreachable" 
      then GetDataFromSecondary() 

     // if primary had a different problem, reraise the error instead of falling back to secondary
      else error e 
in
  Source

An error details records (passed into the one-parameter catch function above, and also included in plain try‘s output, as we’ll learn shortly) will contain the five fields that make up an error in M: Reason, Message, Message.Format, Message.Parameters and Details. This is true even if the record originally used to define the error left off one or more of these fields (remember: they’re optional when defining the error) or if that record included extra fields.

try catch has some interesting syntax rules for the catch function:

  • It must be defined inline. Defining the function elsewhere and then simply referencing it by name isn’t allowed.
  • Its parameter list must be defined using parenthesis syntax. The each shortcut isn’t allowed.
  • Type assertions may not be used in the definition.

These rules make all of the following illegal:

// Not allowed -- catch needs to be defined inline; it cannot be a reference to a function defined elsewhere
let
  ErrorHandler = (e) => ...some code...
in
  try SomeFunction() catch ErrorHandler
// Not allowed -- each cannot be used here
try SomeFunction() catch each ...some code...
// Not allowed - type assertions may not be used here
try SomeFunction() catch (e as record) as any => ...some code...

try

Last but not lease, plain vanilla try evaluates the provided expression, then returns a record with details about the expression’s result.

If the tried expression completed successfully, the record try outputs is in the form of:

[
  HasError = false, 
  Value = (whatever value the tried expression returned)
]

For example:

let
  DoSomething = () => 45,
  Result = try DoSomething()
in
  Result // [HasError = false, Value = 45]

If the tried expression raised an error, the returned record looks like:

[
  HasError = true,
  Error = (error details record)
]

Example:

let
  DoSomething = () => error "bad",
  Result = try DoSomething()
in
  Result
// [
//    HasError = true, 
//    Error = [
//       Reason = "Expression.Error",
//       Message = "bad",
//       Detail = null,
//       Message.Format = "bad,
//       Message.Parameters = null
//    ]]
//  ]

Prior to try catch being added to M, implementing conditional remediation logic required using try with some boilerplate code, resulting in verbose expression like:

let
  Primary = try GetDataFromPrimary(),
  Source = 
    // if primary is good, use what it returns
    if Primary[HasError] = false 
    then Primary[Value] 

    // if the error is because primary is unreachable, fall back to secondary
    else if Primary[Error][Reason] = "External Source Error" and Primary[Error][Message] = "Server is unreachable" 
    then GetDataFromSecondary()

    // if primary had a different problem, reraise the error instead of falling back to secondary
    else error Primary[Error]
in
  Source

try catch achieves the same effect with less code, as this example (repeated from earlier) demonstrates:

let
  Source = 
    try GetDataFromPrimary()
    catch (e) =>
      // if the error is because primary is unreachable, fall back to secondary
      if e[Reason] = "External Source Error" and e[Message] = "Server is unreachable"
      then GetDataFromSecondary() 
 
     // if primary had a different problem, reraise the error instead of falling back to secondary
      else error e 
in
  Source

Again, try catch didn’t used to be part of M. Today, you may still find plain try used to implement adaptive error handling in legacy cases, but hopefully new adaptive handling logic will use the more succinct try catch syntax instead.

Moving forward, you (likely) will see plain try used much less.

Scope

In order have an effect, error handing must occur at a level where the error is encountered. Error handling has no effect on errors that are contained at a different level.

let
  Data = #table({"Amount"}, {{10}, {error "help!"}, {error "save me!"}})
in
  try Data otherwise 0

Result‘s try doesn’t do anything for this mashup. Apparently, the developer hoped it would replace any column errors with zero, but that’s not how it was applied. The way things were wired up, if the expression defining Data raises an error, try will replace that error with zero. However, in this case, Data returns a valid table. True, there are cells in that table with errors, but those errors are contained at the cell level. Since they do not affect Data‘s table-level expression, the try at the table expression level has no practical effect.

try does help with the following, but its effect may not be what the developer intended.

let
  Data = #table({"Amount"}, {{10}, {error "help!"}, {error "save me!"}})
in
  try List.Sum(Data[Amount]) otherwise 0

Above, List.Sum iterates through the values in Data[Amount], adding them up. If an expression defining an item value raises an error, that error is propagated out of List.Sum, causing the summation as a whole to abort. try handles this error, returning 0 in place of the total List.Sum would have output in error-free circumstances.

If that was the intention, great! However, if the intention was to replace any erroring items with 0 while allowing the summation as a whole to complete, try must be applied so that it handles errors at the table cell level—it needs to be wired in to receive errors from column value expressions.

At first glance, Table.TransformColumns(Data, {"Col1", (input) => try input otherwise 0}) might seem like an option. Perhaps surprisingly, this logic does not catch errors raised by column value expressions. Why not? A function’s arguments are eagerly evaluated before their values are passed into the function. If that evaluation results in an error, the function is not invoked so never sees the error; instead, the error is propagated out to the caller. In the case of Table.TransformColumns, if a column value expression raises an error, the transformation function (e.g. (input) => ...) is not called, so its try cannot handle the error; instead, the error is propagated back to Table.TransformColumns.

The problem is that the column value expression needs to be evaluated inside the try. To achieve this, try stepping back to the row level. Wire in a function that receives a reference to the entire row. Then, inside your function, use the row reference to access the column’s value, wrapped in a try expression. Now, any errors raised as a result of that evaluation will be propagated to your try expression which can then handle them appropriately.

It’s not simple, but one of the simplest ways to get a column’s value via a row reference, work with it, then save the resulting output back to the table is to replace the column of interest by using Table.AddColumn followed by Table.RemoveColumns + Table.RenameColumns

let
  Data = #table({"Amount"}, {{10}, {error "help!"}, {error "save me!"}}),
  ErrorsReplacedWithZero = Table.AddColumn(Data, "NewAmount", (row) => try row[Amount] otherwise 0),
  RemoveOldAmount = Table.RemoveColumns(ErrorsReplacedWithZero, {"Amount"}),
  RenameNewAmount = Table.RenameColumns(RemoveOldAmount, {"NewAmount", "Amount"})
in
  List.Sum(RenameNewAmount[Amount]) // returns 10

I agree with you—the above is a complex solution to achieve something that seems like it should be straightforward. If you want to use an elaborate try, unfortunately, some form of working with the table at the row level is required. However, if all you need is to simply replace any error in a particular column with a default value (which is all the above example’s try does), Table.ReplaceErrorValues is your friend.

let
  Data = #table({"Amount"}, {{10}, {error "help!"}, {error "save me!"}}),
  ErrorsReplacedWithZero = Table.ReplaceErrorValues(Data, {{"Amount", 0}}) // replaces any errors in column Amount with 0
in
  List.Sum(ErrorsReplacedWithZero[Amount]) // returns 10

Applying similar behavior to items in a list is more complex. There’s no List.ReplaceErrorValues library function and List.Transform(Data, (input) => ...) doesn’t help for the same reason that Table.TransformColumns doesn’t help with tables. Instead, the simplest solution may be to turn the list into a table, handle the error appropriately, then convert the table back to a list.

let
  Data = {10, error "help!", error "save me!"},
  DataAsTable = Table.FromValue(Data),
  ErrorsReplacedWithZero = Table.ReplaceErrorValues(DataAsTable, {{"Value", 0}}),
  BackToList = ErrorsReplacedWithZero[Value]
in
  List.Sum(BackToList) // returns 10

Rule Violations

You may not find yourself raising errors that often. Typically, the errors you encounter may come from data connectors and library functions. Don’t forget, though, that you can use errors to announce violations of expectations, such as to signify that a particular data item failed to conform to a business rule.

Say you’re processing a CSV file where values in the ItemCode column should always start with an “A”. Early in your mashup, you could check values for conformance to this rule, replacing abnormal values with errors. Later processing steps which access the column will be alerted if they attempt to work with rule-violating values (because of the errors that will be raised).

let
  Data = GetData(), // for testing use: #table({"ItemCode"}, {{"1"}, {"A2"}})
  Validated = Table.TransformColumns(Data, {"ItemCode", each if Text.StartsWith(_, "A") then _ else error Error.Record("Invalid Data", "ItemCode does not start with expected letter", _) })
in
  Validated

This approach may be of particular interest when writing a base query that several other queries will pull from, as it allows you to centralize your validation (think: the DRY principle) while ensuring that attempted users of erroneous data are forcibly alerted to the presence of the anomalies.

By no means is this the only means of centralizing validation logic. Another option is simply to define an extra column for the rule, set to true or false, based on whether the rule is complied with:

let
  Data = GetData(), // for testing use: #table({"ItemCode"}, {{"1"}, {"A2"}})
  Validated = Table.AddColumn(Data, "ValidItemCode", each Text.StartsWith(_[ItemCode], "A"), type logical)
in
  Validated

With this option, logic that cares whether ItemCode is valid is responsible to check ValidItemCode. If the developer forgets to perform this check, invalid data may be treated as valid. In contrast, the replace invalid data with errors approach ensures that logic attempting to access an invalid value is forced to recon with its nonconformance (because the access attempt raises an error).

Whether either of these options is appropriate will depend on your context.

Next Time

That’s it! There you have M’s error handling. Hopefully you don’t encounter too many errors in your Power Query work, but when you do, you know know how to handle them. You also know how to raise your own errors, for cases where you detect an anomaly or need a development placeholder.

Soon the plan is to talk about the behind the scenes way Power Query organizes things (sections) and the faculty M provides to annotate values with additional information (metadata). Before then, though, it’s time to look at how the type system works in Power Query.

Until then, happy coding!

Revision History

  • 2020-02-10: Updated section Scope, explaining how to use a try expressions to handle errors in table cells.
  • 2022-06-09: Revised to reflect M’s new try catch syntax, as well as to incorporate rudimentary mentions of M’s new structured error message functionality.

In this post, I am going to talk about the IFERROR function in Power Query. There is no explicit IFERROR function in Power Query but we have try and otherwise statement which works similar to the IFERROR function in Excel.

IFERROR in Power Query (try and otherwise) – Video

Power Query try and otherwise – Example 1

  1. Consider this sample data. I have Employee ID, Total Pay, and Hours.
  2. I created a Custom Column to calculate Pay per Hour
= [Total Pay] / [Hours]

Simple Data Model with error

Note – Emp ID 6 returns an error because 200 is divided by a text = “nil”. To correct the above error, I am going use try and otherwise within the formula

= try [Total Pay]/[Hours] otherwise null

As an output, I got a null instead of an Error.

Datamodel with null

Power Query try and otherwise – Example 2

The try and otherwise statement can also be applied to the entire step.

  1. I deleted the Custom Column for Pay/Hours
  2. And Changed Type, results in an error since it wasn’t able to find the previous step

Error

This is a step-level error that happened at the Changed Type Step.

To fix this error I will surround my entire formula with the try and otherwise statement in the formula bar.

= try
    Table.TransformColumnTypes(Source, {{'Pay/Hours', type number}})
otherwise
    #'Removed Columns'

If the above formula (which is trying to change the type of Pay/Hours) returns an error then the otherwise statement returns the previous step – Removed Columns. The query throws no errors.

Datamodel Example 2

Power Query try and otherwise – Example 3

This time let’s write the try statement (without otherwise). Let’s calculate Pay/Hours but this time with only the try keyword.

Create a new Custom column with the following formula,

= try [Total Pay]/[Hours]

As a result, it gives me a column that contains records.

Datamodel Record Example 3

Expanding Custom column is going to return 3 more columns.

Datamodel Expanded Custom

  1. HasError – Shows TRUE if the formula resulted in an Error.
  2. Value – Is the output of the formula with no errors.
  3. Error – Again contains records that will describe the error upon expanding.

Upon further expanding the Error column.

Datamodel expanded Error

We again get 3 more columns,

  1. Reason – This tells the reason for the error.
  2. Message – This shows what actually the error is.
  3. Detail – Tells the information of the error, message about the error.

On further expanding Detail column we get even more details about the error on that particular row.

Datamodel Expanded Detail

So this is a very crude way of doing some very basic error reporting on your data in case your Power Query steps result in any errors.

More on Power Query

Promote Double Headers in Power Query

5 Tricks to Reduce Steps in Power Query

Remove Top Rows and Combine Data from Multiple Excel Files

Quick VLOOKUP in Power Query

Dynamic Unpivoting in Power Query

Chandeep

Welcome to Goodly! My name is Chandeep.
On this blog I actively share my learning on practical use of Excel and Power BI. There is a ton of stuff that I have written in the last few years. I am sure you’ll like browsing around.
Please drop me a comment, in case you are interested in my training / consulting services. Thanks for being around
Chandeep

 

s_bag

Пользователь

Сообщений: 31
Регистрация: 29.11.2013

Загружаю текстовый файл с помощью Power Query.
Если сначала удаляю строки, содержащие ошибки в столбце Tran Date, а потом снимаю в фильтре галочку с null, всё Ok.
Если сначала снимаю в фильтре галочку с null, то внизу показывает одну строку с Error, которую удалить нельзя.
И, похоже, часть строк пропадает.
Почему в этом месте важна последовательность действий?
Собственно, обрабатываемый файл:GL Jan-Mar.TXT (17.08 КБ)Спасибо.

 

s_bag, потому что у вас перед этим идет шаг определения типа. на этом шаге у вас возникает ошибка из-за того, что данные (слова «Tran Amount») не распознались как дата.
Если вы не избавились от этой ошибки, то при включении фильтра по null у вас происходит явное вычисление значений столбца для сравнения с nul. Оно длится до первой ошибки, далее операция прерывается, поэтому всё, что после первой ошибки, не показывается. Удалить ее не получается, потому что это такая «вложенная» ошибка.
В общем, обрабатывать ошибки желательно прямо сразу после их возможного возникновения, во избежание

 

Андрей VG

Пользователь

Сообщений: 11878
Регистрация: 22.12.2012

Excel 2016, 365

#4

31.07.2020 20:04:17

Доброе время суток
Может лучше так?

Скрытый текст

 

s_bag

Пользователь

Сообщений: 31
Регистрация: 29.11.2013

Максим, спасибо за разъяснения.
Андрей, это высший пилотаж! Пошел изучать код.

 

Андрей VG

Пользователь

Сообщений: 11878
Регистрация: 22.12.2012

Excel 2016, 365

#6

01.08.2020 11:12:57

Цитата
s_bag написал:
это высший пилотаж

Ну, я бы так не сказал. Только либо имея чёткий стандарт формирования таких файлов, либо достаточную базу фактических файлов, можно сказать достаточно ли для идентификации строки с данными в файле по существованию не пустой строки между двумя символами /.

 

t330

Пользователь

Сообщений: 29
Регистрация: 30.01.2020

Народ, а есть ли в PQ на M возможность прописать обработчик ошибок , который будет например при возникновении ошибки на каком -то шаге , заменять ошибку на null или на что-то другое?

 

Андрей VG

Пользователь

Сообщений: 11878
Регистрация: 22.12.2012

Excel 2016, 365

#8

30.08.2020 06:42:34

Цитата
t330 написал:
Народ

И вам — здравствуйте, человечище!
Что — настолько лень прочитать в спецификации языка

Error Handling

? Microsoft считает, что на это достаточно трёх минут.

 

t330

Пользователь

Сообщений: 29
Регистрация: 30.01.2020

#9

31.08.2020 16:23:10

Андрей, спасибо за ссылку, только я не смог применить …

Например, хочу сделать так, чтобы ошибка для преобразовании типа данных обрабатывалась как в коде ниже, но не срабатывает

Код
let
    Источник = Excel.CurrentWorkbook(){[Name="Таблица3"]}[Content],
    #"Измененный тип" = try Table.TransformColumnTypes(Источник,{{"Num", Int64.Type}}) otherwise Table.TransformColumnTypes(Источник,{{"Num", type text}})
in
    #"Измененный тип"

Прикрепленные файлы

  • ОбработчикОшибок.xlsx (16.58 КБ)

Изменено: t33031.08.2020 16:23:53
(добавлен файл примера)

 

Андрей VG

Пользователь

Сообщений: 11878
Регистрация: 22.12.2012

Excel 2016, 365

#10

31.08.2020 16:53:29

Цитата
t330 написал:
ошибка для преобразовании типа данных

где? По вашему примеру, если поменять имя таблицы на Таблица3_2, то всё прекрасно загружается. Правда, вы сделали sefl reference таблицу, ну, это уже не та проблема.
Или вы хотели разобрать обработку ошибки, когда неверный источник?

 

t330

Пользователь

Сообщений: 29
Регистрация: 30.01.2020

Все равно не срабатывает
Вот видео на 1 минуту  

https://radikal.ru/video/WUyGMi7CDdE

Изменено: t33031.08.2020 17:58:53

 

t330

Пользователь

Сообщений: 29
Регистрация: 30.01.2020

#12

31.08.2020 22:03:43

Цитата
Андрей VG написал:
где? По вашему примеру, если поменять имя таблицы на Таблица3_2, то всё прекрасно загружается. Правда, вы сделали sefl reference таблицу, ну, это уже не та проблема.Или вы хотели разобрать обработку ошибки, когда неверный источник?

Да , если в источнике заменить Таблица3 на Таблица3_2 , то якобы мой неправильный код сработает. На самом деле нет, так как в этом случае в самом источнике (Таблица3_2) в столбце Num просто не возникает никаких ошибок при изменении типа переменной с Text на int.64 , потому что в этом столбце только цифры и пустые ячейки. В общем, мой «обработчик» просто ничего не делает.  

 

Андрей VG

Пользователь

Сообщений: 11878
Регистрация: 22.12.2012

Excel 2016, 365

#13

31.08.2020 22:07:56

Цитата
t330 написал:
не возникает никаких ошибок при изменении типа переменной с Text на int.64

Суть не в этом. ОБработка ошибок начинается только тогда, когда есть ошибка. Table.TransformColumnTypes разве возвращает ошибку? Функция возвращает таблицу. Но вот ошибки содержаться в некоторых или всех ячейках столбца, для которого был указан не верный тип данных. Следовательно, нужно отдельно проверять, содержит ли столбец какую-нибудь ошибку и на основании этого менять указание типа данных. Иначе никак.

 

t330

Пользователь

Сообщений: 29
Регистрация: 30.01.2020

#14

01.09.2020 01:11:21

Цитата
Андрей VG написал:
Следовательно, нужно отдельно проверять, содержит ли столбец какую-нибудь ошибку и на основании этого менять указание типа данных. Иначе никак.

Научите пожалуйста как это сделать?
Вот такой код не работает

И еще вы сказали, что можно сделать обработку ошибок , если к примеру у меня ошибка появляется на уровне слияния запросов из-за того, что например в одном запросе поменялись какие-то источники (поменялся состав столбцов в файлу одного запроса) … Не сочтите за наглость , но как это прописать?

Код
let
    Источник = Excel.CurrentWorkbook(){[Name="Таблица3_2"]}[Content],
    #"Измененный тип" = Table.TransformColumnTypes(Источник, {each try {"Num", Int64.Type} otherwise  {"Num", type text} })
in
    #"Измененный тип"  



Прикрепленные файлы

  • ОбработчикОшибок.xlsx (13.81 КБ)

 

Андрей VG

Пользователь

Сообщений: 11878
Регистрация: 22.12.2012

Excel 2016, 365

#15

01.09.2020 06:51:47

Цитата
t330 написал:
Вот такой код не работает

И не должен. Тут два пути, либо попытаться указать тип числовой и проверить столбец на ошибки, или проверить, что столбец содержит не целые числа (в примере просто числа).

Прикрепленные файлы

  • ОбработчикОшибок.xlsx (20.36 КБ)

 

t330

Пользователь

Сообщений: 29
Регистрация: 30.01.2020

#16

02.09.2020 14:25:18

Цитата
Андрей VG написал:
И не должен. Тут два пути, либо попытаться указать тип числовой и проверить столбец на ошибки, или проверить, что столбец содержит не целые числа (в примере просто числа).

Спасибо!

1 Power Query Ошибки №1. Измененный тип, Неверная фильтрация в UI Почему возникает ошибка Expression.Error Столбец таблицы не найден? Одна из причин — шаг Измененный тип.
Почему в вашей итоговой таблицы не все данные, которые должны там оказаться? Вероятно вы совершили ошибку неверной фильтрации в пользовательском интерфейсе. 2 Power Query Ошибки №2. Фиксированная ширина столбца Excel В этом уроке вы узнаете как отключить автоматическое определение ширины столбцов таблицы Excel при обновлении запроса Power Query. 3 Power Query Ошибки №3. Formula Firewall, запрос ссылается на… Вы работаете в Power Query, но при попытке обновить все возникает ошибка Formula Firewall, запрос ссылается на… Что делать? Как исправить? 4 Power Query Ошибки №4. Доступ к веб-содержимому В этом уроке вы узнаете как избавиться от надоедливого окна доступ к веб-содержимому. 5 Power Query Ошибки №5. Количество столбцов CSV При импорте CSV получается неверное количество столбцов. 6 Power Query Ошибки №6. Не получается изменить тип данных (DataFormat.Error) При попытке изменить тип данных с текстового на дату возникает ошибка DataFormat.Error. 7 Power Query Ошибки №7. Разные имена листов В этом уроке мы решим проблему, когда в ваших источниках нужные вам данные находятся всегда на листах с разными именами. 8 Power Query Ошибки №8. Разные имена листов 2 В этом уроке мы другими способами решим проблему, когда в ваших источниках нужные вам данные находятся всегда на листах с разными именами. 9 Power Query Ошибки №9. Разные имена столбцов, Table.TransformColumnNames В этом уроке мы разберем проблему, когда нам присылают файлы, в которых всегда столбцы названы по-разному. 10 Power Query Ошибки №10. Как развернуть все столбцы В этой задаче мы научимся разворачивать все столбцы табличного столбца не перечисляя каждый из них хардкодом. 11 Power Query Ошибки №11. Подключиться к последнему файлу В этом уроке мы научимся подключаться к самому свежему файлу из нужной нам папки с нужным названием. 12 Power Query Ошибки №12. Консолидация и MissingField.Type Бывает вы ходите выполнить добавление таблиц друг под другом с предварительной обработкой, но получаете ошибку Expression.Error: Столбец таблицы не найден. 13 Power Query Ошибки №13. Удалить пустые столбцы Как без хардкода автоматически удалить пустые столбцы в Power Query, которые есть в импортируемой таблицы. Разберем 2 способа. Один из них прост до безобразия, а для второго понадобится функция Table.Profile. 14 Power Query Ошибки №14. Удалить лишние пробелы В этом уроке мы разберем еще 1 способ удалить лишние пробелы в текстовом столбце Power Query. Для этого мы повторим и изучим несколько новых функций.
Text.SplitAny
List.Select
Character.FromNumber
Text.Combine 15 Power Query Ошибки №15. Плохо структурированный TXT Как быть, если текстовый файл разбивается на столбцы неправильно? 16 Power Query Ошибки №16. При округлении не совпадает общая сумма Разберем ошибку, которая возникает при необходимости разбить число на определенные доли, а результаты округлить до двух знаков после запятой. В такой ситуации может получиться, что сумма слагаемых не будет равняться исходному числу. 17 Power Query Ошибки №17. Удаление дубликатов, Подводные камни Table. Distinct Что может пойти не так при использовании функции удаления дубликатов. 18 Power Query Ошибки №18. Удаление дубликатов 2, Подводные камни Table. Distinct Что может пойти не так при использовании функции удаления дубликатов. Другое решение данной проблемы. 19 Power Query Ошибки №19. Неверная разбивка на столбцы При импорте данных из CSV или TXT разбивка на столбцы происходит неправильно. 20 Power Query Ошибки №20. Пустая таблица при импорте Excel (XLSX, XLS) Вы пытаетесь подключиться к книге Excel, но импортируется пустая таблица. 21 Power Query Ошибки №21. Подключение к PostgreSQL, Установка драйвера Вы пытаетесь подключиться к PostgreSQL и получаете ошибку «Перед использование этого соединителя необходимо установить один или несколько дополнительных компонентов»

Понравилась статья? Поделить с друзьями:
  • Power query как заменить error на null
  • Power query загрузка завершилась ошибкой
  • Power query если ошибка формула
  • Power query xlsb error
  • Power query expression error столбец таблицы не найден