Complex Filter Conditions In Power BI Reports Using Visual-Level Filters And Measures

While Power BI has a lot of great functionality for filtering the data that appears in a visual using visual-level filters there are going to be situations where you need more control over how filtering works, and in this blog post I’m going to show you how you can use visual-level filters and measures in combination to achieve this. The example I show here may be quite specific but I think the general technique has a lot of other interesting applications.

Let’s say you have a table containing customer purchases, with potentially more than one purchase per customer, and want to display a table in Power BI showing only the most recent purchase for each customer relative to a given date.

Using data from the Adventure Works DW database, here’s the Internet Sales table from my Power BI example dataset that contains the customer purchases. Each purchase has a Sales Order Number and a Sales Order Line Number, and there may be multiple line items in a single sales order.

image

Here’s some data from this table, filtered down to show the purchases of one customer: Abby Subram.

image

[Note: all dates shown are in dd/mm/yyyy format]

The requirement is that the report user should be able to select a date in a slicer and show the details for the most recent sales order that occurs before the selected date. So, for example, if the user selects 1/1/2003 then no rows should appear; if 16/6/2003 is selected then only SO50934 should appear; if 30/9/2003 is selected only the four rows relating to SO54975 (and not SO50934) should appear; if 28/6/2004 is selected only the four rows relating to SO73938 should appear, and so on.

The first step is to create a create a date table that contains the ‘as of’ dates the user can select from, using the disconnected slicer technique: basically a date table that has no relationship at all to the Internet Sales table shown above.

image

Next comes the tricky part. Visual-level filters can be used to filter the contents of visuals in a Power BI report based on the value of a measure, and that measure does not need to be displayed in a report. What you therefore have to do is create a measure that will return the value 1 for data that should be displayed (in this case, the most recent purchase order for the customer) and blank otherwise, then apply a visual-level filter using this measure and filter on this measure equalling 1. Marco and Alberto have a great article going into the details of how measures behave when they are used in visual-level filters here that I recommend you read before carrying on.

For this particular scenario here’s my measure:

[sourcecode language=”text” padlinenumbers=”true”]
Is Latest SO =
VAR CurrentAsOfDate =
SELECTEDVALUE ( ‘As Of Date'[DateKey] )
VAR CurrentSODate =
SELECTEDVALUE ( ‘Internet Sales'[OrderDateKey] )
VAR CurrentCustomer =
SELECTEDVALUE ( ‘Internet Sales'[CustomerKey] )
VAR SameCustomerSOs =
FILTER (
ALL ( ‘Internet Sales'[CustomerKey], ‘Internet Sales'[OrderDateKey] ),
‘Internet Sales'[CustomerKey] = CurrentCustomer
&& ‘Internet Sales'[OrderDateKey] <= CurrentAsOfDate
&& ‘Internet Sales'[OrderDateKey] > CurrentSODate
)
VAR NoLaterSOs =
IF ( COUNTROWS ( SameCustomerSOs ) > 0, BLANK (), 1 )
RETURN
IF ( CurrentSODate <= CurrentAsOfDate, NoLaterSOs )
[/sourcecode]

The logic here is:

  • Assume that this measure will be used to filter a table visual in a Power BI report, where each row in the table visual displays data from a single row in the Internet Sales table in the dataset.
  • For each row in the Internet Sales table, find the current values for OrderDateKey and CustomerKey, as well as the date selected in the ‘as of date’ slicer, and store them in variables
  • Create a table of all possible combinations of CustomerKey and OrderDateKey ignoring the current filter context, then filter it so you get all the sales orders for the current customer and where the order date is greater than the date of the current sales order and less than or equal to the selected ‘as of’ date. This is stored in the SameCustomerSOs variable.
  • If there are rows in the SameCustomerSOs table then the current row in Internet Sales should not be displayed because it is not the most recent sales order as of the selected date; if SameCustomerSOs is empty then the current row should be displayed, because there are not later sales orders for the current customer before the selected as of date.
  • Finally, return 1 if SameCustomerSOs is blank and if the current row in Internet Sales has an order date before the selected as of date.

You can then create a table (or some other visual) in your Power BI report and, in the visual-level filters for that table, drag in the measure and apply a filter on the value of the measure equalling 1:

image

Here’s the result, showing the data for Abby Subram:

 

MostRecentPurchase

The one thing you do need to be careful of when using this technique is that the DAX you use in your measure must be as efficient as possible: inefficient DAX, complex filters and large data volumes will make your report unusably slow. I’m sure the DAX used in my measure above could be tuned to perform better (it currently takes around half a second to filter the full 60,000 rows in the Internet Sales table; Power BI only queries for and displays 500 rows at a time, which makes things faster) but the real point I wanted to make here is that using measures and visual-level filters in this way allows you to apply almost any complex filter condition you want to the data displayed in a visual.

You can download the sample pbix file for this post here.

9 thoughts on “Complex Filter Conditions In Power BI Reports Using Visual-Level Filters And Measures

  1. Hi Chris, I think there is something missing in the FILTER() function in the ‘SameCustomerSOs’ variable declaration?
    – Jes.

    1. Thanks for spotting that – yes, there was some extra code with characters that got lost when I published. Hopefully you can see the correct code now.

    1. Nice solution – you should blog about it! Yes, I guess this technique could be applied to the top n with others problem, but I your solution is easier to understand and implement.

  2. Hi Chris – could you expand on how you implemented the separate ‘Month’ slicer dropdown? I’ve seen some methods for doing this with a separate date table and a calculated column and was wondering if this was your method, or if you had figured out something more elegant. Afraid I can’t download your PBIX file. Thanks in advance!

  3. Hi Chris, thanks for the great pattern. I am working with a similar problem that I’m struggling with and wondering if you had ideas. Using your data, how would you change the measure if instead of just the most recent transaction of 1 customer, you instead wanted to see an average of the filtered sales amounts for each customer, *and* then you wanted to see just those averaged amounts that are above a user-selected cut-off value (eg just those above $1000). So the problem I am having is that the first filter steps have to happen before the average of the sales per customer gets calculated, but the second step, filtering those resulting averages by a second user-selected value, is where I get stuck. Any ideas? (note that we can get this to work by added the average amount to the visual filter, but that doesn’t work well in my case. I need the user to be able to select 1 cut-off value and have several visuals across several pages be filtered automatically).

  4. Hey Chris,

    Hope you are doing good.

    I was working on a measure to display TOP 3 sales based on Products.

    I am not getting the expected result, while storing the aggregated value of a Gross Sales in a variable and then trying to use it in TOP N function.
    I am getting the same value similar to the sum of Gross Sales and not the TOP 3 products.

    However the same works when I create a ‘separate measure’ to store the Gross Sales aggregation and then using that measure in my TOP N calculation.

    Could you please explain me about this behavior.

    Measure with the unexpected result when the Aggregation stored in a variable and used in TOP N part: 

    Top3ProductSales =

    VAR RankingText=VALUES(Products[Products])

    VAR TotalSales=SUM(financials[Gross Sales])

    RETURN
    CALCULATE(

    TotalSales,
    TOPN(3,
    ALL(Products[Products]),
    TotalSales),
    RankingText)

    Measure with expected results:

    “First Measure”

    TotalGross Sales = SUM(financials[Gross Sales])

    “Second Measure for TOP N calculation”

    Top3ProductSales =

    VAR RankingText=VALUES(Products[Products])

    RETURN
    CALCULATE(
    [TotalGross Sales] ,
    TOPN(3,
    ALL(Products[Products]),
    [TotalGross Sales] ),
    RankingText)

Leave a Reply to IgorCancel reply