Forum Discussion

AlexisOlson's avatar
AlexisOlson
Icon for Super User rankSuper User
3 years ago
Solved

No CALCULATE Challenge -- Round #2

I learned a lot from the brilliant folks who participated in my previous challenge, so I wanted to explore another situation where I find CALCULATE highly useful and see how others manage to do without it.

 

In this scenario, I want to define a benchmark ratio as the ratio of sales to sales in North American Stores, preserving all filtering other than geography and channel. Using CALCULATE, this is simple:

 

Sales Benchmark Ratio = 
VAR _BenchmarkSales =
    CALCULATE (
        [Sales Amount],
        ALL ( Geography ),
        Geography[ContinentName] = "North America",
        ALL ( Channel ),
        Channel[ChannelName] = "Store"
    )
VAR _Ratio = DIVIDE ( [Sales Amount], _BenchmarkSales )
RETURN
    _Ratio

 

 The challenge is to write a measure without CALCULATE that can reproduce this robustly.

 

Here's an example of what the values should be with some filters applied:

 

For full credit, the measure should work regardless of what dimensions are used for slicers and what dimensions are used for rows and columns of the matrix. It should still work even if new dimensions are added to the data model.

Participants from last time:

tamerj1 AntrikshSharma Greg_Deckler Anonymous

 

Other friends who might like to join:

BA_Pete bcdobbs CNENFRNL smpa01 parry2k ValtteriN 

  • AlexisOlson and All,

    I was completely convinced that No CALCULATE solution wasn't possible for such kind of problems. My belief is that filter context manipulation is exclusively CALCULATE branded. Yesterday while handling a DAX issue, AlexisOlson No CALCULATE challenge popped up in my mind. With a probable solution, I decided to try the first thing in the morning and seems it covers all the requirements.

    Sales Benchmark Ratio 2 = 
    VAR _BenchmarkSales =
        SUMX (
            ALLSELECTED ( Geography[RegionCountryName] ),
            SUMX ( 
                CROSSJOIN (
                    TREATAS ( { "North America" }, Geography[ContinentName] ),
                    TREATAS ( { "Store" }, Channel[ChannelName] )
                ),
            [Sales Amount]
            )
        )
    VAR _Ratio =
        DIVIDE ( [Sales Amount], _BenchmarkSales )
    RETURN
        _Ratio

30 Replies

  • tamerj1's avatar
    tamerj1
    Icon for Community Champion rankCommunity Champion

    AlexisOlson and All,

    I was completely convinced that No CALCULATE solution wasn't possible for such kind of problems. My belief is that filter context manipulation is exclusively CALCULATE branded. Yesterday while handling a DAX issue, AlexisOlson No CALCULATE challenge popped up in my mind. With a probable solution, I decided to try the first thing in the morning and seems it covers all the requirements.

    Sales Benchmark Ratio 2 = 
    VAR _BenchmarkSales =
        SUMX (
            ALLSELECTED ( Geography[RegionCountryName] ),
            SUMX ( 
                CROSSJOIN (
                    TREATAS ( { "North America" }, Geography[ContinentName] ),
                    TREATAS ( { "Store" }, Channel[ChannelName] )
                ),
            [Sales Amount]
            )
        )
    VAR _Ratio =
        DIVIDE ( [Sales Amount], _BenchmarkSales )
    RETURN
        _Ratio
    • AlexisOlson's avatar
      AlexisOlson
      Icon for Super User rankSuper User

      tamerj1 Nice work! This does appear to satisfy the requirements, so I'll accept it. The trick here is using the implicit CALCULATE context transition that comes from referencing another measure, [Sales Amount].

      For an extra challenge, can anyone modify this so that it works as a standalone measure? Maybe using SUMMARIZE since that can also do a context transition.

      • tamerj1's avatar
        tamerj1
        Icon for Community Champion rankCommunity Champion

        AlexisOlson 
        Yes sure, I thought about that already but it does not look so nice

        Sales Benchmark Ratio 2 = 
        VAR _BenchmarkSales =
            SUMX (
                SUMMARIZE ( 
                    CROSSJOIN ( 
                        ALLSELECTED ( Geography[RegionCountryName] ),
                        CROSSJOIN (
                            TREATAS ( { "North America" }, Geography[ContinentName] ),
                            TREATAS ( { "Store" }, Channel[ChannelName] )
                        )
                    ),
                    Geography[RegionCountryName],
                    Geography[ContinentName],
                    Channel[ChannelName],
                    "@Amt", SUM ( Sales[SalesAmount] )
                ),
            [@Amt]
            )
        VAR _Ratio =
            DIVIDE ( [Sales Amount], _BenchmarkSales )
        RETURN
            _Ratio
    • Greg_Deckler's avatar
      Greg_Deckler
      Icon for Community Champion rankCommunity Champion

      tamerj1 Brilliant! Nice one Tamer. Going to add that one to my pattern of No CALCULATE tricks.

      • tamerj1's avatar
        tamerj1
        Icon for Community Champion rankCommunity Champion

        Greg_Deckler 

        Shall we expect a new one of your brilliant videos? Tag me if you do 😉

  • CNENFRNL's avatar
    CNENFRNL
    Icon for Community Champion rankCommunity Champion

    Interesting challenge ðŸ‘‹ Looking forward to some tricky solutions as I've already spent more than half hour but no luck. I really doubt there exists an elegant, error-proof alternative, especially when some new slicers will intervene.

     

    Off the track comment on CALCULATE

    I admit that the idea to kick the ass of CALCUALTION is buzzy at the first glance; but with further comprehension of DAX, I'd like to say the mechanism of context transition triggered by CALCULATE is a genius one! It differentiates DAX from other traditional "context insensitive" lauguages like Excel formulas, SQL etc.

    • AlexisOlson's avatar
      AlexisOlson
      Icon for Super User rankSuper User

      Yeah, this example demonstrates what I consider the most important use case for CALCULATE -- modifying filter context in a simple and precise way that works in almost any context without needing revision. One can write a measure without CALCULATE that works for a specific visual but if it can't reliably be reused in other contexts, we're back to the bad old days of SSRS where we often need separate queries for each visual.

       

      Much of my day-to-day work is building and maintaining common data models that feed dozens of thin reports  (I don't know in advance every possible context that each measure will be used) and this type of situation (where just one or two filters need to be removed or adjusted) comes up frequently with benchmarks and the only way I can think to do it without CALCULATE is remove all filters and specify the ones I want to put back, which requires (as far as I can tell) including within the measure definition any dimension table I ever want to filter or slice on, which is a problem if new dimensions are ever introduced (and probably not great for performance either).

  • Greg_Deckler's avatar
    Greg_Deckler
    Icon for Community Champion rankCommunity Champion

    AlexisOlson Technically meets the letter of the law with regard to the challenge. 🙂

    Sales Benchmark Ratio NC Cheat = 
    VAR _BenchmarkSalesTable =
        CALCULATETABLE(
            'Sales',
            ALL ( Geography ),
            Geography[ContinentName] = "North America",
            Channel[ChannelName] = "Store"
        )
    VAR _Ratio = DIVIDE ( [Sales Amount 1], SUMX(_BenchmarkSalesTable,[SalesAmount]) )
    RETURN
        _Ratio

     

    • ValtteriN's avatar
      ValtteriN
      Icon for Community Champion rankCommunity Champion

      Expanding on this logic. I think one solutions would be to use functions such as  NATURALINNERJOIN to create a similar table and then use SUMX. This was my first instinct when looking at this (assuming CALCULATETABLE is also forbidden). Otherwise, your solution should be the best.

  • Greg_Deckler's avatar
    Greg_Deckler
    Icon for Community Champion rankCommunity Champion

    AlexisOlson This one works but is slow. Sharing it in case someone has a brilliant idea on how to optimize

    Sales Benchmark Ratio NC2 = 
        VAR __Products = DISTINCT('Product'[ProductKey])
        VAR __Promotions = DISTINCT(Promotion[PromotionKey])
        VAR __GeoKeys = DISTINCT(SELECTCOLUMNS(FILTER(ALL('Geography'), [ContinentName] = "North America"), "GeoKey",[GeographyKey]))
        VAR __Stores = DISTINCT(SELECTCOLUMNS(FILTER(ALL(Stores), [GeographyKey] IN __GeoKeys),"StoreKey",[StoreKey]))
        VAR __Channels = DISTINCT(SELECTCOLUMNS(FILTER(ALL('Channel'), [ChannelName] = "Store"),"ChannelKey",[Channel]))
        VAR __BenchmarkSalesTable = 
            FILTER(
                FILTER(
                    FILTER(
                        FILTER(
                            ALL('Sales'),
                            [ProductKey] IN __Products
                        ),
                        [PromotionKey] IN __Promotions
                    ),
                    [StoreKey] IN __Stores
                ),
                [channelKey] IN __Channels
            )
        VAR __BenchmarkSales = SUMX(__BenchmarkSalesTable, [SalesAmount])
        VAR __Ratio = DIVIDE ( [Sales Amount 1], __BenchmarkSales )
    RETURN
        __Ratio
    • AlexisOlson's avatar
      AlexisOlson
      Icon for Super User rankSuper User

      Greg_Deckler This is exactly the sort of approach I mentioned above and does not qualify as a full solution.

       

      the only way I can think to do it without CALCULATE is remove all filters and specify the ones I want to put back, which requires (as far as I can tell) including within the measure definition any dimension table I ever want to filter or slice on, which is a problem if new dimensions are ever introduced (and probably not great for performance either).
      • Greg_Deckler's avatar
        Greg_Deckler
        Icon for Community Champion rankCommunity Champion

        AlexisOlson Understood but first step is to at least get something that returns the same values under the same conditions.

         

        Also, not to get all nerdy but your calculation can't guarantee that adding additional dimensions will not break the calculation either. For example, if I add a GeographyType dimension and wire it to the Geography table and add the necessary rows for Stores, for example, a store tied to geography code 422 which is a State/Province and a Sales fact row for that Store then your calculation would always include that store row even when I specifically filter out State/Province in a slicer. So it's kind of a bogus requirement that the No CALCULATE version has to be better than the CALCULATE version.

  • tamerj1's avatar
    tamerj1
    Icon for Community Champion rankCommunity Champion

    Hi AlexisOlson 

    Doesn't seem that simple 😅

    it's 8:15pm right now here and I don't have access to my laptop. I hope not to close the subject until tomorrow same time. I will try to give it a shot anyway from my phone. 
    I just wanted to say that even this CALCULATE version of the measure won't work in all scenarios. For example if you have Country as a slicer or if you have any attribute from the Channel table other than Channel Name in the rows, I guess it won't work. So please be nice and grant a little bit of flexibility 😎

    • AlexisOlson's avatar
      AlexisOlson
      Icon for Super User rankSuper User

      Adding a Country slicer doesn't seem to break it as far as I can tell but fair point about the Channel. I didn't use another ALL since there's only one visible column from that table, but I'll go ahead and edit that in.

  • tamerj1's avatar
    tamerj1
    Icon for Community Champion rankCommunity Champion

    AlexisOlson 

    Not the best performance for sure 

    Sales Benchmark Ratio =
    VAR _BenchmarkSales =
    SUMX (
    FILTER (
    ALLSELECTED ( Sales ),
    RELATED ( Geography[ContinentName] ) = "North America"
    && RELATED ( Channel[ChannelName] ) = "Store"
    ),
    Sales[Quantity] * Sales[Price]
    )
    VAR _Ratio =
    DIVIDE ( [Sales Amount], _BenchmarkSales )
    RETURN
    _Ratio

    • CNENFRNL's avatar
      CNENFRNL
      Icon for Community Champion rankCommunity Champion

      Inspiring way addressing this tricky chanllenge; but I'm afraid

      FILTER(
          ALLSELECTED( Sales ),
          RELATED( Geography[ContinentName] ) = "North America"
              && RELATED( Channel[ChannelName] ) = "Store"
      )

      would produce an empty table because there exist an external slicers Channel[ChannelName] and Geography[ContinentName]; when Channel[ChannelName]<>"Store" or Geography[ContinentName]<>"North America" are selected, the filtered table is empty.

  • Anonymous's avatar
    Anonymous
    Not applicable

    AlexisOlson - OK, interesting....fair or foul to tinker with the data model and collapse the geography snowflake to simplify?

    • AlexisOlson's avatar
      AlexisOlson
      Icon for Super User rankSuper User

      Collapsing the snowflake to a star schema is fine.

      • Anonymous's avatar
        Anonymous
        Not applicable

        Cool, thanks.  Would you mind throwing the data file into your original post?

  • AntrikshSharma's avatar
    AntrikshSharma
    Icon for Community Champion rankCommunity Champion

    AlexisOlson Great challenge, but because of the requirement I don't think it is possible to make the solution dynamic, so CALCULATE wins, Yay!

     

    Otherwise with the current status of the report I would write this:

    Sales Benchmark Ratio 2 = 
    VAR SalesAmount = [Sales Amount]
    VAR _BenchmarkSales = 
        FILTER ( 
            ALL ( Sales ),
            RELATED ( Geography[ContinentName] ) = "North America"
                && RELATED ( Channel[ChannelName] ) = "Store"
                && RELATED ( 'Product'[ClassName] ) IN VALUES ( 'Product'[ClassName] )
                && RELATED ( Promotion[DiscountPercent] ) IN VALUES ( Promotion[DiscountPercent] )
        )
    VAR Result = 
        DIVIDE ( 
            SalesAmount, 
            SUMX ( _BenchmarkSales, Sales[SalesAmount] ) 
        )
    RETURN Result