Power BI Antipatterns #11: Hiding 0 in DAX

Measures that return 0 may be undesirable in certain cases, like when 0 isn’t an interesting value and you’re only interested in non-zero values. In Power BI Antipatterns #7, we saw how you can show 0 instead of BLANK; this blog post shows a common (bad) way of hiding 0 and two better ways.

Here’s a typical antipattern implementation:

Example bad = 
IF(
    SUMX(
        Sales,
        IF(
            RELATED('Product'[Color]) = "Red",
            Sales[Unit Price] * Sales[Order Quantity],
            0
        )
    ) = 0,
    BLANK(),
    SUMX(
        Sales,
        IF(
            RELATED('Product'[Color]) = "Red",
            Sales[Unit Price] * Sales[Order Quantity],
            0
        )
    )
)

Note how in the formula above, we’re repeating a good chunk of code. The actual formula inside IF doesn’t matter; what matters is that it’s repeated exactly within the same formula. First we check if some value is zero, and if it is, then we’re returning BLANK, otherwise we return that value.

Better ways

Experienced DAX writers are probably itching to say that a variable would be appropriate here, and I agree of course:

Example good 1 = 
VAR RedSales =
    SUMX(
        Sales,
        IF(
            RELATED('Product'[Color]) = "Red",
            Sales[Unit Price] * Sales[Order Quantity],
            0
        )
    )
VAR Result =
    IF(
        RedSales <> 0,
        RedSales
    )
RETURN
    Result

Note: why am I using a Result variable when I could just write its contents after RETURN? It’s a good practice for debugging purposes: in case I need to return the result of a different variable, I only need to change the variable reference in RETURN without copying/pasting larger sections of code.

In addition to using a variable, I flipped the check in IF: instead of checking if something is 0, I’m checking whether something isn’t equal to 0, and that allows me not to specify the third parameter in IF, because a missing third parameter in IF is the same as BLANK, just shorter.

The only problem with the formula above is its performance: it’s suboptimal. In case the performance is more important than readability, you can use the DIVIDE trick, which I learned from Greg Baldini:

Example good 2 = 
VAR RedSales =
    SUMX(
        Sales,
        IF(
            RELATED('Product'[Color]) = "Red",
            Sales[Unit Price] * Sales[Order Quantity],
            0
        )
    )
VAR Result =
    RedSales * DIVIDE(RedSales, RedSales)
RETURN
    Result

What’s going on here? Why are we dividing a value by itself and then multiply by the same value? If a value is

  • 0 or blank, then DIVIDE will return BLANK; 0 multiplied by BLANK is BLANK, which is what we want in this case.
  • Non-0, then mathematically, DIVIDE will return 1; any value multiplied by 1 is the value itself.

Somewhat surprisingly, this formula performs better because there’s no IF — just maths.

Again, the problem of [Example good 2] is its readability: no matter what you say, it’s more difficult to read than [Example good 1], however, [Example good 2] is more performant. So you should decide between performance and readability yourself and choose the appropriate technique, now that you know both ways.

Download sample file: Episode 11.pbix

To be continued…

  • Joe JJ

    Do you know how the efficiency of SUMX(IF()) compares to the equivalent CALCULATE(SUMX(),FILTER())?

    • Pretty sure that the latter will be most efficient in most (if not all) cases 🙂 I used SUMX here as a placeholder for any measure.

    • Jes Hansen

      Hi Joe and Daniil,
      if you are curious as to how a CALCULATE solution will fare compared to the measures in the blog post, you can use the following measure, that is semantically similar to what is shown in the blog post.

      CALCULATE (
      SUMX (
      Sales,
      Sales[Unit Price] * Sales[Quantity]
      ),
      KEEPFILTERS ( 'Product'[Color] = "Red" )
      )

      If you use DAX studio you can measure the measures with the following DAX query, alternatively you can use the performance analyzer in Power BI desktop.

      DEFINE
      MEASURE sales[example bad] =
      IF (
      SUMX (
      Sales,
      IF (
      RELATED ( 'Product'[Color] ) = "Red",
      Sales[Unit Price] * Sales[Quantity],
      0
      )
      ) = 0,
      BLANK (),
      SUMX (
      Sales,
      IF (
      RELATED ( 'Product'[Color] ) = "Red",
      Sales[Unit Price] * Sales[Quantity],
      0
      )
      )
      )
      MEASURE Sales[Example good 2] =
      VAR RedSales =
      SUMX (
      Sales,
      IF (
      RELATED ( 'Product'[Color] ) = "Red",
      Sales[Unit Price] * Sales[Quantity],
      0
      )
      )
      VAR Result =
      RedSales
      * DIVIDE (
      RedSales,
      RedSales
      )
      RETURN
      Result
      MEASURE sales[Calc_and_Keepfilters] =
      CALCULATE (
      SUMX (
      Sales,
      Sales[Unit Price] * Sales[Quantity]
      ),
      KEEPFILTERS ( 'Product'[Color] = "Red" )
      )

      EVALUATE
      SUMMARIZECOLUMNS (
      ROLLUPADDISSUBTOTAL (
      'Product'[Brand],
      "brand-RU",
      'Product'[Color],
      "color-RU"
      ),
      ROLLUPADDISSUBTOTAL (
      'Date'[Calendar Year],
      "year-RU",
      'Date'[Calendar Year Month],
      "month-RU"
      ),

      // ***
      // *** Comment and uncomment here to measure the individuel measures ***
      // ***

      "Example good 2", [Example good 2]
      // ,
      // "example bad", [example bad]
      // ,
      // "Calc_and_Keepfilters", [Calc_and_Keepfilters]
      )

      I have used a Contoso database with 12 mio. rows and a version of the DAX engine similar to the one in SSAS 2019. The number of rows in the fact table and the number of roll-up levels have an impact here. (At least in the SSAS 2019 version of the DAX engine). Some table- and column names may vary from the versions of demo databases you are using.

      @daniilmaslyuk:disqus , It’s actually interesting to see how much impact using variables have from the bad to the good examples. Really good pedagogical way to show this.

      Best regards Jes.

      • Thanks for your comment, @JesDKE:disqus! I probably should’ve provided more details in the blog post too — I’ll keep your feedback in mind for future material 🙂