Power BI DAX Calculate Evaluation Order

Comecei a fazer uns testes simples com DAX e Calculate pra entender exatamente, passo a passo, como o DAX e a CALCULATE() funcionam.

Para esse exercício vamos considerar o seguinte cenário:

clique pra aumentar

Perceba que temos:

  • uma tabela fato chamada mini
  • uma dimensão chamada dim_dish
  • relacionamento 1:N entre dimensão e fato
  • X, um visual de tabela com a coluna dim_dish[dish] e a medida [UnitsCalculate FilterRawTable Dim]
  • Y, um visual de tabela com a coluna mini[dDish] e a medida [UnitsCalculate FilterRawTable Dim]
  • uma medida [UnitsCalculate FilterRawTable Dim]

Pergunta

Por que no no visual X são exibidas três linhas, cada uma com 1 na medida e no visual Y apenas uma linha é visualizada?

Resposta

Porque no visual X o eixo é feito na coluna da dimensão, enquanto que no visual Y o eixo é feito na fato.

Por “eixo” eu chamo a coluna que define o contexto da medida, que no caso é a coluna da esquerda, “dish” ou “dDish”.

Explicando

Isso acontece por conta de como funciona a Evaluation Order (ordem de avaliação) da CALCULATE(), que é:

  1. avalia argumentos de filtros
  2. realiza transição de contexto
  3. avalia modificadores de contexto
  4. aplica argumentos de filtro e keepfilters

Essa avaliação acontece uma vez pra cada datapoint. Na prática, usando nosso cenário como exemplo o que vai acontecer é o seguinte:

No visual X:

visual X
  1. Existe um filtro externo, que é dim_dish[Dish] = “Burger” que vem por conta do eixo de Dish que você vê no visual. Então ,esse filtro vai ser aplicado e passado pra dentro da sua medida.
    1. Lá dentro da sua medida, o calculate vai receber uma tabela expandida que está com um filtro em dim_dish[Dish], beleza, guarda esse informação.
    2. Então o calculate vai avaliar o argumento de FILTER(), que retorna um filtro que é uma tabela de uma linha e uma coluna, que é dim_dish[Dish] = “Salad”. Guarda isso na memória.
  2. Realiza transição de contexto, que não existe aqui nesse cenário, então não faz.
  3. avalia modificadores de contexto, nesse caso aqui não temos, então não faz nada.
  4. aplica os argumentos de filtro, então nesse momento o argumento que temos é dim_dish[Dish] = “Salad” que é aplicado ao 1o argumento, o SUMX(). A tabela dentro do SUMX(), mini, que é a tabela expandida que está com um filtro no dim_dish[Dish] = “Burger”, vai sofrer um overwrite (sobreescrita) da calculate nessa coluna, passando dim_dish[Dish] = “Salad”.
  5. Esse filtro modifica a tabela expandida, e nessa tabela o SUMX() performa sua expressão, que no caso é somar Units, logo, o resultado vai ser 1.

Desenhando:

Primeiro nós precisamos entender qual é a tabela expandida que estamos tratando, no caso é essa:

A tabela expandida é como o DAX enxerga sua fonte de dados pra realização dos seus cálculos.

Então, lá no primeiro datapoint, que é o Burger de dim_dish[Dish], a visão que vai ser passada pra dentro da nossa medida é uma tabela expandida já filtrada, assim:

tabela expandida modificada pelo filtro externo do primeiro datapoint

Daí, dentro da medida, CALCULATE() primeiro vai avaliar o FILTER(), e no caso o FILTER() ele dá um ALL() em dim_dish[Dish], pegando Salad, e aí ele vai guardar na memória essa informação.

Perceba que em momento nenhum o FILTER() ALL() alterou a tabela temporária expandida, ele apenas faz isso pra conseguir guardar na memória o filtro que desejamos passar no primeiro argumento da CALCULATE().

Com esse filtro em mãos a CALCULATE() vai executar o overwrite nas colunas que são determinadas nos argumentos de filtro. Então, vai acontecer o seguinte:

CALCULATE() dá overwrite nas colunas que estão nos argumentos de filtro

Depois CALCULATE() aplica os filtros dos argumentos de filtros na tabela temporária expandida.

tabela temporária expandida resultante da calculate

Essa é a tabela temporária expandida que CALCULATE() vai passar para o seu primeiro argumento. SUMX() então recebe exatamente essa tabela temporária pra realizar seu cálculo, que no caso é a soma de mini[Units], o resultado vai ser 1.

Portanto, esse resultado 1 será passado de volta para o datapoint, e no seu visual vai mostrar 1 para Burger, porque a CALCULATE() sobreescreveu o filtro que existia na coluna dim_dish[Dish].

E no visual Y?

E no visual Y? Porque o comportamento é diferente no visal Y?

Porque no visual Y a coluna que está sendo afetada pelo filtro externo não é a mesma que utilizamos na CALCULATE().

No visual Y o filtro externo vem da coluna mini[dDish], e como essa coluna não é utilizada nos argumentos de filtro da CALCULATE() ela não é sobreescrita, ou seja, CALCULATE() não faz overwrite na coluna mini[dDish].

Se desenharmos o que acontece no visual Y, vai ficar assim:

Portanto, o resultado será 1 apenas quando dDish for igual a Salad e dim_dish[Dish] também for igual a Salad. Por isso que no visual aparece 1 apenas para o datapoint de Salad no visual X.

DaxStudio

Utilizando o DaxStudio fica legal pra gente ver como funciona essa ideia do filtro externo. O filtro externo ele é como se fosse um CALCULATE() envolvendo a nossa medida, ou seja, um CALCULATE() dentro do outro e, quando isso acontece, a ordem de precedência, ou o evaluation order do CALCULATE(), executa PRIMEIRO o filtro mais externo antes.

Olha o exemplo no DaxStudio:

medida original

Essa é a medida original, é o mesmo que temos no nosso cenário original:

O que acontece quando a gente tem um filtro externo, é isso aqui:

visual X, primeiro datapoint

Então perceba, existe uma CALCULATE() por fora da CALCULATE() passando um filtro, que é o Burger, usando a coluna da dimensão, como no visual X. Veja que o resultado é Salad = 1. Se eu colocar ali Pasta, Burger, Salad, qualquer resultado vai ser 1, por conta do overwrite que a CALCULATE() mais interna realiza antes de criar a tabela que será utilizada pelo SUMX().

Agora vamos ver como ficaria se fosse como no visual Y.

visual Y, primeiro datapoint

Note que, quando alterei o filtro externo da primeira CALCULATE() para uma coluna diferente da utilizada no argumento de filtro da CALCULATE() interna, o resultado ficou como BLANK, porém, se eu passar no filtro externo Salad, o resultado vai ser 1.

Por que? Porque existe uma interseção que resulta verdadeiro somente nesse caso, conforme observamos de acordo com os registros da tabela expandida e também conforme já expliquei ao longo desse post!

Conclusão

Espero que isso possa ajudar a todos a entender melhor como funcionam a ordem de avaliação da CALCULATE()!

Dúvidas ou sugestões, deixem aí no comentário!

Referências

https://www.sqlbi.com/articles/order-of-evaluation-in-calculate-parameters/

https://www.sqlbi.com/blog/marco/2010/01/03/how-calculate-works-in-dax/

https://www.sqlbi.com/articles/expanded-tables-in-dax/

Published by Pedro Carvalho

Apaixonado por análise de dados e Power BI

Deixe uma resposta

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: