The blank value in DAX is a special value requiring particular attention in comparisons. It is not like the special null value in SQL, and it could appear in any conversion from a table expression. This article explores in details the behavior of the blank value in DAX, highlighting a common error in DAX expressions using SWITCH.
What is a blank value in DAX
Any column other than a Boolean (True/False) data type in DAX can have a blank value. This is the value assigned to a column when the data source contains a NULL value. If you are used to SQL, you might expect a propagation of the blank value in a DAX expression, but in reality the behavior is different. When you evaluate a DAX expression, a blank value is always converted to 0 or to an empty string, depending on the data type requested by the expression, unless it is evaluated in any term of a multiplication, in which case the blank value propagates in the multiplication result.
You can obtain a blank value in DAX calling the BLANK function. The following table shows what is the result of several expression containing a blank value. You can replace the call to the BLANK function with any DAX expression returning a blank value, including a column reference.
Expression | Result |
---|---|
BLANK () |
Blank value |
BLANK () = 0 |
True |
BLANK () && TRUE |
False |
BLANK () || TRUE |
True |
BLANK () + 4 |
4 |
BLANK () - 4 |
-4 |
4 / BLANK () |
Infinite |
BLANK () * 4 |
Blank value |
BLANK () / 4 |
Blank value |
INT ( BLANK () ) |
Blank value |
Comparisons with BLANK
When you use BLANK in a comparison, the conversion to 0 or to an empty string results in a positive match with these values. For this reason, comparing an expression to blank requires a specific function, ISBLANK, which returns true whenever its only argument is a blank expression. The next table shows a number of examples that illustrates how you obtain a positive match comparing a blank values with an empty string and a zero value.
Expression | Result |
---|---|
IF ( BLANK (), "true", "false" ) |
false |
IF ( ISBLANK ( BLANK () ), "true", "false" ) |
true |
IF ( BLANK () = True, "true", "false" ) |
false |
IF ( BLANK () = False, "true", "false" ) |
true |
IF ( BLANK () = 0, "true", "false" ) |
true |
IF ( BLANK () = "", "true", "false" ) |
true |
The automatic conversion to zero or empty string of a blank value might have undesired side effects. For example, consider the following expression and the result for different values of [measure].
IF ( [measure] = BLANK(), "is blank", IF ( VALUE ( [measure] ) = 0, "is zero", "other" ) )
[measure] | Result |
---|---|
0 | is blank |
“” | is blank |
BLANK | is blank |
All the three values (zero, empty string, and blank) match the first condition of the two IF statements, and the second IF is never executed.
If you invert the two comparisons, now there are two values (zero and blank) that match the first condition of the two IF statements, and the second is never executed.
IF ( VALUE ( [measure] ) = 0, "is blank", IF ( [measure] = BLANK(), "is zero", "other" ) )
[measure] | Result |
---|---|
0 | is blank |
“” | error |
BLANK | is blank |
In this case, the measures containing an empty string generates an error during evaluation, because it cannot be converted to a valid value by the VALUE function. When you use nested IF functions, the order of comparisons to equivalent BLANK values is important, because only the first one matches the comparison.
When you use SWITCH, the behavior is different, because it applies a different comparison logic that matches the BLANK value using the ISBLANK function internally. As you can see in the following table, the result of a comparison to an expression that evaluates to BLANK matches the first blank or false value found in the list.
Expression | Result |
---|---|
SWITCH ( BLANK(), 0, "zero", BLANK(), "blank", True, "true", False, "false" ) |
blank |
SWITCH ( BLANK(), 0, "zero", True, "true", BLANK(), "blank", False, "false" ) |
blank |
SWITCH ( BLANK(), 0, "zero", True, "true", False, "false", BLANK(), "blank" ) |
false |
It is interesting that the comparison with False might get precedence. In order to avoid any issue, the comparison with BLANK should be the first one in the switch list.
Automatic conversion from table expressions
A table expression converted to a scalar value generates a BLANK value when the table is empty. This is particular important for functions as COUNTROWS. For example, the following FILTER always return an empty table, so the result provided by COUNTROWS in the following example is blank instead of zero. You have to add a zero value in case you want to make sure the result is a number that you can use in a SWITCH function, as you see in the following example.
Expression | Result |
---|---|
COUNTROWS ( FILTER ( Table, False ) ) |
Blank value |
COUNTROWS ( FILTER ( Table, False ) ) + 0 |
0 |
The previous example is important to understand why a COUNTROWS used in a SWITCH statement might result in unexpected behavior. You can see that in the following table the first example shows a common error, assuming that the expression returns 0 whereas it returns blank, which does not match any value and returns the “other” string. The second example matches the blank value, and the third example matches the zero value by summing zero to the result of COUNTROWS.
Expression | Result |
---|---|
SWITCH ( COUNTROWS ( FILTER ( Query1, False ) ), 0, "zero", 1, "one", "other" ) |
other |
SWITCH ( COUNTROWS ( FILTER ( Query1, False ) ), BLANK(), "blank", 0, "zero", 1, "one", "other" ) |
blank |
SWITCH ( COUNTROWS ( FILTER ( Query1, False ) ) + 0, BLANK(), "blank", 0, "zero", 1, "one", "other" ) |
zero |
Understanding how DAX handles blank value is important when you compare values of expressions, in order to avoid possible coding errors caused by wrong initial assumptions.