- Ciao friends.
In this video,
I want to show you how to use the KEEPFILTERS function
in order to correctly compute values
when you need to handle arbitrarily shaped sets.
Now, what the heck is an arbitrarily shaped set?
It is a specific shape of the filter context
that you can either create by writing DAX code,
or because you use a slicer on top of a hierarchy of columns
instead of a single column.
So as soon as you have arbitrarily shaped sets in your code,
you need to pay extra attention whenever you work with them.
Because if you do not fully understand
how the filter contexts are merged together,
it is likely your measures might compute wrong results.
And fixing them requires understanding where the problem is
and how to solve it.
Keep in mind that this is not one of my usual videos.
In most of my videos I spend most of the time writing
DAX code in order to show you how to solve a scenario,
this is not one of those.
Here, in the entire video,
we will write only one line of DAX code.
The rest of the time is spent in understanding
why that single line is so important,
and will solve our problem.
With all that said,
let's start by looking together at the scenario.
I have a report, which is slicing by year,
the sales amount,
I actually have the year and the month.
And I have a slicer.
Now, the slicer is filtering the hierarchy year and month.
So out of year and month,
I select November and December in 2007,
and January and February in 2008.
So I have four different months,
but in two different years.
For the sales amount, everything is working as expected.
But then I want to create another calculation
which is the monthly average.
Monthly average needs to compute
the sales amount month by month,
and then provide the average of all these values.
So I'm using AVERAGEX over the values of the 'Date'[Month],
and then call the measure, [Sales Amount].
Because of context transition,
[Sales Amount] computes the value month by month,
and AVERAGEX takes care of computing the average.
If I take this measure,
and drop it in the report,
you see that the numbers of the month levels are correct.
And also at the year level,
this number is a reasonable average
of these other two values.
The problem is of the grand total,
1.7 million cannot be in any way
the average of values which are way below one million.
So, that is preposterous.
I mean, this looks like one of those DAX shenanigans
that are carefully designed
in order to drive developers crazy.
At least, that's one way of looking at the problem.
There is another way of looking at the same scenario,
which is that maybe, just maybe,
we are not fully understanding how the filter context
is being built inside the iteration,
and some issue is happening because of the code we wrote,
and not because of DAX.
So, we will need to understand
exactly how this filter context is created,
and how the calculation happens in every individual part.
That requires some time and some theory.
Yes, I know, boring theory.
But it's the kind of theory that is actually needed
in order to understand how to fix this specific problem.
And, by the way, also to understand a bit better
how iterations and filter context are merged together.
We will need to start from the very basics.
That is, we start to understand
what happens when you apply filters on individual columns,
and then we will move on our specific scenario,
where we have the arbitrarily shaped set,
and we will understand the details
of how the filter context is built during the iteration.
Let's get started.
Let me draw first some details about it.
As I said, we need to start with two filters
on two different columns,
and this is exactly what I have here.
I have one slicer that is filtering the year,
and another slicer that is filtering the month.
Now, these two slicers place filters in the filter context.
The first slicer applies a filter on the year column,
and filters 2007 and 2008.
This is a filter applied to the filter context.
The same happens for the month.
I have the month column,
for which I select January and December.
Again, another filter placed in the filter context.
Now that I have two filters in the filter context,
which roles will be visible in my date table?
Well actually, when DAX needs to compute this value,
it does a CROSSJOIN, so it builds the CROSSJOIN
of all the possible values.
So, this will generate a filter context
that contains both the year and the month.
Actually, it does not generate a filter context,
but it makes visible in the filter context
roles from the date table that contain 2007, January,
that is a good role.
And 2007, December, again, that is a good value.
The same happens for 2008,
both January and December are good values.
So, these two filters are equivalent,
the green filter and the blue filters,
they are actually the same.
And this is a scenario that happens
when you have simple filters.
Whenever you have filters on one column only,
this is the scenario you have,
and everything is simple and easy to understand.
The problem is, in our scenario,
we do not have a simple filter.
We actually have an arbitrarily shaped set.
Let's see that in more detail.
This is the report we have, and you see that
the slicer is not filtering two columns separately.
It is filtering the year and the month together.
So, November and December are visible for 2007,
whereas January and February are visible for 2008.
This is what is called an arbitrarily shaped set.
And arbitrarily shaped sets creates this problem.
In order to understand where the problem is,
and to debug it, we first need to focus
on the cell that contains a problem.
These numbers are correct.
All these numbers as we said are correct.
The problem appears here, 1.709.
Here is where the value is wrong.
So, in order to understand exactly what is happening there,
we first draw the filter context
that is active in that cell,
then we follow the code step by step
in order to understand how the code is being generated.
So, let's start by creating the filter context.
I need to show you the details.
The filter context as we said is generated by this slicer.
Let's draw it.
So the filter context contains the year,
and it also contains the month,
both in the same filter.
Now, for the year we have 2007,
and for the month, November and December.
So we have 2007 November, and 2007 December.
Whereas for the 2008, we have January and February.
We have January and 2008 February.
Okay, now, this is the filter context that is active
in the cell where we have our problem.
Now let's look at the code.
First we have AVERAGEX.
AVERAGEX is an iterator.
Being an iterator, it iterates its first argument,
and its first argument is VALUES of 'Date'[Month].
Let's highlight it.
What does VALUES of 'Date'[Month] return?
Well, we can just look at here, we have one, two, three,
four different values for the month.
Therefore AVERAGEX will iterate four times,
once for each month.
Let's say that we are positioned here,
so the row context is pointing to the first month, November,
and we start computing our measure.
Now, our measure is [Sales Amount].
Because it's a measure, we know that CALCULATE is there.
Inside the measure we are using CALCULATE,
just because it's a measure,
and CALCULATE is always there when we use a measure.
CALCULATE among the many operation it does,
it performs context transitions.
So it will take the row context
which is pointing to November,
and generate a new filter context that contains November.
So we have month, November.
And this is the regional filter context,
and this is the filter context...
after context transition.
So, we have one filter coming from the slicer,
a new filter coming from CALCULATE,
and the engine now needs to merge the two filters together
in order to generate the new filter context
under which it computes sales amount.
How does the merging of two filter context happen?
Well CALCULATE says you are filtering the month
in the new filter context,
remember that this is the new filter context,
you're filtering the month in the new filter context,
and it happens that you are filtering the month
also in the old filter context.
So in order to merge the two contexts,
because the new filter context
has the option of overriding the old filter context,
CALCULATE will remove any reference
from the old filter context
and then merge the two together.
Now, what happens when the two filters are now,
what is the final result of the filter context?
We have a filter on the year,
which is what remains of the old filter context,
that filter 2007 and 2008.
Any reference to the month disappear from here.
And this filter is copied as it is,
we have the month, which contains November.
Now, this is the new filter context which is being used
when [Sales Amount] is being computed.
So during this calculation here,
we are using this filter context
that filters two columns, the year and the month.
Now, what happens when you filter two columns?
Exactly what we have drawn at the very beginning.
You have the CROSSJOIN of all the possible values,
which in our case, means we are seeing 2007 November
and 2008 November together.
The original shape of the filter context
that was filtering November only in 2007 is completely lost.
What actually happens during the calculation
is that the engine goes on the first month,
let's say now it's January,
and it computes this value,
the sum of 2007 and 2008 together.
That leads to this value.
Then on the second iteration, it does this operation,
then it does this other operation,
and finally computes this operation.
So the numbers which are being aggregated are these numbers,
which lead to an average which is 1.7 million,
that is the same value we have seen at the beginning.
So the problem here is not DAX.
The problem is that we started
from an arbitrarily shaped set,
and when calculated during context transition,
override operator to replace
the month part of the old filter context,
it destroyed the arbitrarily shaped set and transformed it
into a regular set.
So we no longer have information
about the relationship between the years and the months.
That is the problem.
Now how do we solve it, how can we make sure
that the original shape of the arbitrarily shaped set
is not lost, does not get lost during the calculation?
Let's go back to our original scenario.
This is just what we wrote earlier,
it's just more readable.
Let me enlarge it a bit.
This is the original filter context,
let's say original filter.
And this is the new filter.
What we need to do is to tell CALCULATE,
"Hey, when you will merge the two filter context together,
please do not override, don't do that.
Avoid doing this operation, but instead,
just sum together the two filters,
so that these filters is used as a single filter
that keeps the old filter context
and only adds the new filter context to the filter."
So what we want to do is to keep this filter,
and add this filter.
There is a specific function that does it,
which is KEEPFILTERS.
KEEPFILTERS tells CALCULATE that we do not want
to override the old filter context,
we want to keep it, and then add the further filters
without removing the old filter.
As soon as we use KEEPFILTERS, as you will see,
the problem will disappear.
Let's do that.
I still have my monthly average measure here.
Now I don't want to override this measure,
so let me copy the code and we create a new measure,
that we call Monthly Average KEEPFILTER.
And here is the first problem.
Where do I add CALCULATE here?
Where do I add KEEPFILTERS?
There is no CALCULATE anywhere.
Actually, we know there is one CALCULATE
very well hidden here,
but we would like to add KEEPFILTERS here,
but KEEPFILTER is a CALCULATE modifier,
and CALCULATE is not visible here,
it is hidden inside context transition.
That is why KEEPFILTER can be used as a CALCULATE modifier,
but also as the top level function in iterators.
If you use KEEPFILTERS that way,
this KEEPFILTER will change the way
context transition happens
when iterating over the values of the 'Date'[Month],
and this KEEPFILTER will be actually used here
where we have the measure reference.
Let me just hit enter, then we need to select the measure,
and format it, I just want to use the decimal format.
Now I can put my measure here, add commas,
and you see that now the numbers are correct.
Let me get rid of this filter.
Now the numbers are correct.
I see 768, which is the correct average of all the values
which are shown month by month.
So KEEPFILTER solved our problems.
Now, I know, you are asking yourself
when to use KEEPFILTERS and when not.
Answer to this and a lot of important details
about using KEEPFILTERS with arbitrarily shaped set
is present in the article on the SQLBI website.
So if you find this topic interesting,
make sure to go to the SQLBI article,
read it, download the file, and start playing with it.
In general, think that whenever you have a problem with DAX,
or whenever the numbers are not correct,
there are no dark forces in action.
It just that, you probably have not understood well
how the filter context has been created,
or the specific calculation that is creating your issues.
So whenever that happens,
you need to do exactly what we did here,
slow down, take a long breath, maybe a cup of coffee,
and then write down the filter context
at the beginning of your form,
and then step by step,
follow how the filter context is being created.
The answer to your wrong number is likely to lie
somewhere in the process of building the filter context,
and fixing is only a matter of knowing
how to make a filter context do exactly what you need.
Enjoy DAX.