top of page
  • Writer's pictureJason Cockington

When What-If Won’t Work

Downunder DAX is a blog about all things Power BI, but to date, all of the articles that have been posted have been Power Query related. That being the case, this week I decided to change tack and focus on a DAX topic, with a write up of a question I was asked during one of our In-House private training courses.

The question was looking at solving the problem of post-sales support costs that change price over time due to the rate being charged for the support.

Below is the question summarized, plus a strategy to solve it.

Download the Sample Workbook if you'd like to follow along.

 


The Question

Let’s assume the example:

Rate 0.7 valid before 01/07/2017

Rate 0.8 valid between 01/07/2017 – 3/08/2018

Rate 0.9 valid afterwards

 

Is it possible to create a helper table containing rate information and get DAX to choose the correct rate to be applied to a calculation based on the time period?

 

What about What-If?

If you have had some experience with solving DAX problems already, you might be thinking that we could solve this problem with a What-If parameter.

While the What-If parameter is excellent for introducing a dynamic value into a calculation, it is limited in that it will apply to the entire calculation. For example, if I was to create a Total Support Cost calculation as Total Support Cost = [Support Minutes] * [Rate], where Rate represents a What-If value, then as we change the Rate from 70c to 80c, that new rate will be applied to the entire data set, including the dates where the rate was 70c.

To solve this particular challenge we need a different approach.

DAX Variables to the Rescue  

The easiest way to solve this problem is by breaking it into pieces and the trick to that is taking advantage of Variables in DAX. 

Understanding and implementing variables is an intermediate level concept of DAX, so don’t worry if it feels a little over your head at this point, you can always come back to this later, and I promise to write an article about Variables that will link back to this post.

 

Creating the Helper Table

The first step in solving this problem is creating the helper table that is going to contain the Rate and Date information. To add the new RateChange table to my data model I will go to the Home ribbon and click the Enter Data button. 


A 3-row table containing Rates and Dates

This will be a disconnected table in the Semantic Model.


5-table Star Schema with 1 disconnected table


Identifying the Current Filter Date 

To identify which rate would be selected for any given date we will start a new measure designed to detect the Selected Rate.

The first Variable for the Selected Rate measure needs to identify the date of interest, and to achieve that we'll use SELECTEDVALUE. 


Selected Rate =

VAR ThisDate = SELECTEDVALUE('Calendar'[Date])

RETURN

ThisDate


With the date now in scope, the next step requires we filter the RateChange table to return the appropriate Rate based on the date selected in ThisDate.

To understand the logic required for this step, we’re going to look at what sort of filters we need to apply to the RateChange table to “reshape” it for the calculation.


Understanding the Filtered Table

The second variable in our calculation is going to be a virtual table. It is not always easy to understand what a virtual table is going to look like, and thus we are going to use the Data View to help us understand what will happen inside our virtual table calculation.

In the Data View, go to the RateChange table. Click the down arrow on the Valid From column (1), run your mouse over the Date Filters (2) and select Custom Filter… (3).


How to set a Custom Filter on a table

The rate first changed when the date was July 1st, 2017, so in the Custom Filter wizard, set the filter to be is on or before the 9 March 2017.


Custom Filter input wizard showing date is on or before

This returns a single Valid From date, January 1, 2016.

Now update the custom filter to is on or before the 3/7/2017.  This time the table returns 2 dates (rows).


2-row table showing Rates & Dates after a custom filter has been applied

This means we are going to have to use two steps in order to select the correct rate.  The first step will be targeted to filtering the table, the second step will select the correct date value from the filtered table.


Filtered Table Variable

In the illustration above, we discovered that if the RateChange table is filtered on or before (<=) the 30th of June 2017, the table is left with a single row, but if it is filtered on or before the 2nd of August 2018 it will return two rows, and thus if it is filtered on or after (>=) the 3rd of August 2018, it will return three rows. Therefore, in our FilteredTable variable we need to apply the same “custom filter” logic to the table. 


Simply apply a FILTER on the RateChange table where the Valid From date is <= ThisDate.

 

Selected Rate =

    VAR ThisDate = 'Calendar'[Date] 

    VAR FilteredTable = FILTER(RateChange, RateChange[Valid From] <= ThisDate)

        RETURN

        ThisDate


Although we are now successfully filtering the table we cannot visualise it inside the measure because measures return scalar values, and this FilteredTable variable returns a table of values. This is why we stepped through the filtering examples in the data view above.


Selecting the Date to Use

Now that we are able to sense the current filter's date, and we have a virtual copy of our disconnected RateChange table that will be filtered based on the current filter date, the next step we need is to ensure we have just one date available to work with from our RateChange, so we can use this to select the correct rate to apply into our Total Cost of Support calculation. 

For this we’ll take the largest Valid From value by Calculating the MAX Valid From date, filtered by the FilteredTable variable.


Selected Rate =

    VAR ThisDate = SELECTEDVALUE('Calendar'[Date])  

    VAR FilteredTable = FILTER(RateChange, RateChange[Valid From] <= ThisDate)  

    VAR Date2Use = CALCULATE(MAX(RateChange[Valid From]), FilteredTable)  

    RETURN

        Date2Use


Next, set up a Table visual on your report canvas, with Date and Selected Rate to test the result of the measure. In the table visual, we can confirm the measure is working correctly because the date being returned by Selected Rate switches on the 1st of July, 2017.


Table visual displaying the Date2Use change over on July 1st

Identifying the Rate to Use

Lastly we need to select the rate based on our Date2Use. Again, we’ll use a SELECTEDVALUE for this.

 

Selected Rate =

    VAR ThisDate = SELECTEDVALUE('Calendar'[Date])

    VAR FilteredTable = FILTER(RateChange, RateChange[Valid From] <= ThisDate)

    VAR Date2Use = CALCULATE(MAX(RateChange[Valid From]), FilteredTable)  

    VAR Rate2Use = CALCULATE(SELECTEDVALUE(RateChange[Rate]), RateChange[Valid From] = Date2Use)

    RETURN

        Rate2Use

 

In the Table visual we can see that the rate is switching from 70c to 80c on the 1/7/2017, and again on the 3/8/2018.


Table visual displaying Rate2Use change over dates

 

Problem with Filter Granularity

Now that we can identify the correct rate we need to apply to our calculation we can use the SelectedRate as the multiplier in our Total Cost of Service, however if we simply multiply the Support Minutes by the Selected Rate things don’t work quite the way we might expect.


Total Support Cost = [Support Minutes] * [Selected Rate]


Everything works at the day level of granularity fine:

 

Total Support cost has a valid value when Table visual filters by date

 

But if I switch the Date for the Year and Month I get different behaviour:

 

Total Support Cost returns blank when Table visual filters by Month

 

What’s happening here?

Why are we getting blanks?

 

The issue is to do with the SELECTEDVALUE.  Now that the visual is no longer looking at each day, each row of the table now representing a single month, there are multiple dates selected in the current filter, and multiple dates means the alternate value for the SELECTEDVALUE function is being returned. In other words, the calculation is now Support Minutes multiplied by BLANK.

 

In order for the calculation to work correctly we need to ensure the Support Minutes multiplied by the Selected Rate is calculated day by day, and then added together to give the solution at month or year granularity.

This means we’ll need to iterate over the Calendar table, one day (row) at a time, and calculate the Support Minutes multiplied by the Selected Rate for that day.  Then we’ll need to sum these values together at the end to return the correct Total Support Cost for each month or year as required.

The key to enabling this iteration is an X function, and because we need to sum the values together at the end, we’ll use SUMX.


Total Support Cost formula including SUMX IntelliSense

And now the formula works correctly:


Improved Total Support Cost returns a valid result at the Month level

 

Solving in a Single Calculation

This is great and sufficient for resolving the immediate need, but for completeness of the lesson, I’d like to evaluate the entire result as a single calculation rather than rely on the Selected Rate interim measure, but I can’t just add the Total Support Cost calculation as another variable in the formula because I’ll get the Blank issue again:


Total Support Cost 2 =

    VAR ThisDate = SELECTEDVALUE('Calendar'[Date])

    VAR FilteredTable = FILTER(RateChange, RateChange[Valid From] <= ThisDate)

    VAR Date2Use = CALCULATE(MAX(RateChange[Valid From]), FilteredTable)  

    VAR Rate2Use = CALCULATE(SELECTEDVALUE(RateChange[Rate]), RateChange[Valid From] = Date2Use)

    VAR CurrentRowSupport = SUMX('Calendar', [Support Minutes] * Rate2Use)

    RETURN

        CurrentRowSupport

 

Total Support Cost 2 shows blanks when Table visual filters by Month

This is because within a measure a variable actually behaves as a constant, and thus once a variable has been calculated and assigned it will not change.  Hence, the SUMX isn’t yielding the desired iterative effect on Rate2Use, because ThisDate was already calculated based on multiple dates, and so BLANK is being passed to each subsequent Variable.

The solution is to wrap all of the variables inside SUMX.  This then forces the variables to be calculated for each row of the Calendar, just like what was happening when we place the interim measure Selected Rate inside the SUMX:


Complete Total Support Cost 2 formula including comments to explain each variable

Final version of Total Support Cost 2 returns valid results when Table visual is filtering by Month

 

I’d encourage you to step through the process of writing the DAX yourself, but if you want to copy the code:

Total Support Cost 2 =

SUMX('Calendar',

    VAR ThisDate = 'Calendar'[Date] 

    VAR FilteredTable = FILTER(RateChange, RateChange[Valid From] <= ThisDate)

    VAR Date2Use = CALCULATE(MAX(RateChange[Valid From]), FilteredTable)  

    VAR Rate2Use = CALCULATE(SELECTEDVALUE(RateChange[Rate]), RateChange[Valid From] = Date2Use)  

    VAR CurrentRowSupport = [Support Minutes] * Rate2Use  

    RETURN

        CurrentRowSupport

)


Conclusion

While the What-If parameter is an excellent tool for introducing a user-defined dynamic value into a calculation of a Power BI report, it is limited in the sense that as the value changes, it is applied to the entire dataset. This is fine when you are looking to perform simple scenarios such as 'what-if sales increase by x%'. When there is a need to provide different values to the calculation based on a specific combination of filters, then the What-If parameter is no longer sufficient, and we need an alternate approach.

In the example above, I leverage the power of Variables, Disconnected Tables, an X-Function (iterating) and the FILTER function to feed a different rate into the Total Support Cost calculation based on the period of time that is on display in the report.

While the example above has been simplified for this article, the concepts covered by this technique can be applied to help solve more complex real-world challenges such as calculating the Total Sales value when the Product Price changes over time or across different geographical regions.

bottom of page