CALCULATE e Context Transition

Potrebbe interessarti:

Se vuoi imparare a preparare, modellare, visualizzare e analizzare i dati aziendali in modo efficace, dai un’occhiata al corso Introduzione a Power BI, e al Corso Avanzato Power BI DAX.

Dimmi di Più

Cos’è la Context Transition

In DAX, il termine “Context Transition” (Transizione del Contesto) indica il processo attraverso cui un Row Context viene trasformato in un Filter Context equivalente. Quando avviene questa transizione, i valori delle colonne della riga corrente diventano veri e propri filtri attivi sul modello, influenzando la valutazione dell’espressione che segue. La Context Transition si verifica in modo automatico in due situazioni:

  1. quando esiste un Row Context (ad esempio creato da un iteratore come SUMX, FILTER, ADDCOLUMNS o presente nella definizione di una Calculated Column) e viene chiamata la funzione CALCULATE;

  2. quando, sempre all’interno di un Row Context, viene invocata una Measure, che implicitamente applica una Context Transition prima di essere valutata. Infatti, quando scriviamo una Measure come:

    // Measure
    Total Quantity = SUM(Sales[Order Quantity])
    

    il motore DAX internamente esegue qualcosa di concettualmente equivalente a:

    // Measure
    Total Quantity = CALCULATE(SUM(Sales[Order Quantity]))
    

Il Row Context itera, ma non filtra. il Filter Context filtra, ma non itera. Trasformando il Row Context in un Filter Context, possiamo filtrare i dati del Data Model usando i dati del record referenziato dal Row Context corrente.

In sintesi, la Context Transition permette di trasformare una logica “per riga” in una logica “per filtro”, consentendo una valutazione coerente con la riga corrente, ma basata sui meccanismi di filtro tipici delle Misure e del motore DAX. Riassumendo, abbiamo questo schema:

// generates a Row Context...
Iterator or Calculated Column ->
    // inside a Row Context...
    "Row Context" ->
        // CALCULATE, executed inside a Row Context,
        // triggers the Context Transition
        // that transforms the current Row Context into a Filter Context
        { CALCULATE() or [Measure] } ->
            "Context Transition" ->
                "Row Context to Filter Context"

Il nuovo Filter Context così generato include tutte le colonne considerate dal Row Context originale. Se il Row Context sta iterando su una sola colonna, sarà solo quella colonna a entrare nel Filter Context. Se invece il Row Context sta iterando su un’intera tabella composta da più colonne, tutte le colonne della riga corrente verranno incluse nel Filter Context appena creato.

Qualche Esempio

Ora possiamo esaminare alcuni esempi di espressioni DAX che, durante la loro esecuzione, attivano una Context Transition. Questo ci permetterà di vedere come un Row Context venga trasformato in un Filter Context, e quali effetti produca all’interno dei calcoli.

Context Transition Esplicita

Una Context Transition esplicita si verifica quando il codice DAX richiede esplicitamente la trasformazione del Row Context in Filter Context, richiamando la funzione CALCULATE (o CALCULATETABLE) all’interno di un contesto di riga già esistente. Consideriamo un Data Model con una Fact Table Sales, e una Dimension Table Customer che filtra la tabella delle vendite, e guardiamo il codice di questa Misura:

 1// Measure
 2Sales Wealthy Customers =
 3
 4    // sum all Customers' sales, considering only customers who spend more than €10,000.
 5    SUMX(
 6        // iterate over the CustomerKey's distinct values.
 7        // Each RowContext contains the current [Customer Key] value
 8        VALUES(Customer[Customer Key]),
 9
10        // --- Expression Starts ---
11        // Compute the Sales for the current Customer.
12        VAR customerSales =
13
14            // The CALCULATE function triggers a Context Transition
15            // where the "Customer Key" Row Context becomes now the Filter Context.
16            CALCULATE(
17
18                // this second SUMX is executed in a Filter Context that filters
19                // for the current "Customer Key". Thus, it computes the Total Sales
20                // for the current Customer
21                SUMX(
22                     // Sales table, filtered by the current "Customer Key"
23                     Sales,
24
25                     // Sales for the current record
26                     Sales[Order Quantity] * Sales[Unit Price]
27                )
28            )
29
30        // consider the Sales for current Customer only if they are bigger than  €10.000.
31        // If not, return 0
32        VAR Result = IF (customerSales >= 10000, customerSales, 0)
33        RETURN
34            Result
35        // --- Expression Ends ---
36    )

In relazione alla Transizione del Contesto, qui accadono tre cose interessanti:

  1. a linea 5, la funzione di iterazione SUMX itera su ogni riga della tabella con un unica colonna ritornata dall’espressione VALUES(Customer[Customer Key]) di linea 8. Questa contiene i valori distinti di [Customer Key], e per ognuna di queste righe, l’iteratore crea un nuovo Row Context;

  2. a linea 16, la funzione CALCULATE, invocata in presenza di un Row Context, genera una Context Transition. Il Row Context definito da [Customer Key] diventa il Filter Context che filtra i record che verranno processati dalla SUMX interna, chiamata a linea 21.

  3. La SUMX interna, passata come primo parametro di CALCULATE, calcola il totale delle vendite solo per il cliente corrente, perchè la tabella Sales viene filtrata per il valore Customer[Customer Key] corrente.

In questo esempio, la CALCULATE di linea 16 è equivalente a:

 1CALCULATE(
 2    // expression to calculate
 3    SUMX(
 4        // Sales table
 5        Sales,
 6
 7        // Sales for the current record
 8        Sales[Order Quantity] * Sales[Unit Price]
 9    ),
10
11    // boolean filter that filters Sales table by the current Customer Key
12    Sales[Customer Key] = currentCustomerKey
13)

Context Transition Implicita

Una Context Transition implicita è la trasformazione automatica del Row Context in Filter Context che avviene quando una Measure viene invocata all’interno di una Calculated Column. In questo caso, la transizione non è richiesta esplicitamente dal codice DAX scritto dallo sviluppatore, ma è eseguita dal motore DAX in modo automatico. Nell’esempio seguente, definiamo una nuova Calculated Column nella tabella dimensionale Customer, che chiama la Misura Sales Amount. In questo caso, la presenza della Transizione di Contesto non è così evidente:

1// Calculated Column that invokes the Measure "Sales Amount Measure".
2// Invoking a Measure means calling CALCULATE on that Measure.
3// Thus, CALCULATE triggers the the Context Transition
4// BEFORE the execution of the Measure.
5Sales Amount CalcCol = [Sales Amount Measure]

dove la Misura Sales Amount Measure è definita come:

1// Measure
2Sales Amount Measure = SUM(Sales[Sales Amount])

Questo accade in quanto:

  • quando definiamo una Calculated Column, siamo automaticamente all’interno di un Row Context implicito, valido per ogni riga della tabella in cui la colonna viene creata. Questo Row Context viene gestito dal motore DAX e non richiede alcuna istruzione esplicita da parte nostra.

  • all’interno della colonna calcolata, il codice richiama la misura Sales Amount Measure. Ogni volta che una Measure viene invocata in presenza di un Row Context, DAX la valuta passando in automatico attraverso una chiamata implicita a CALCULATE. È proprio questa chiamata implicita che attiva la Context Transition, trasformando i valori della riga corrente in filtri sul modello prima di calcolare la misura.

    Il risultato è che, per ogni riga della tabella Customer, la cella della colonna Sales Amount Measure conterrà l’importo delle vendite totali per quel cliente:

Esempio di Context Transition Implicita
Figura 1: Esempio di Context Transition Implicita

In questo caso, la colonna calcolata Sales Amount CalcCol è equivalente a questo codice:

Sales Amount CalcCol =
    VAR CustomerField1 = Customer[CustomerField1]
    VAR CustomerField2 = Customer[CustomerField2]
    ...
    VAR CustomerFieldN = Customer[CustomerFieldN]
    RETURN
        CALCULATE (
            Sales[Sales Amount],
            Customer[CustomerField1] = CustomerField1,
            Customer[CustomerField2] = CustomerField2,
            ...
            Customer[CustomerFieldN] = CustomerFieldN,
        )

dove i campi CustomerField1...N, sono i nomi dei campi della tabella Customer.

Altri Esempi di Context Transition

Prendiamo in esame queste cinque espressioni DAX, due Calculated Column e tre Measure (nota: [Total Cases] è una Measure):

// Case #1: Calculated Column
Column 1 = CALCULATE(               // Row Context -> CALCULATE -> CT
    SUM(Winesales[CASES SOLD]))

// Case #2: Calculated Column
Column 2 = [Total Cases]            // Row Context -> CALCULATE "Total Cases" -> CT

// Case #3: Measure
Measure 1 =
    AVERAGEX(Wines, [Total Cases])  // Row Context -> CALCULATE "Total Cases" -> CT

// Case #4: Measure
Measure 2 =
    AVERAGEX(                       // Row Context -> CALCULATE -> CT
        Wines,
        CALCULATE(
            SUM(Winesales[CASES SOLD])))

// Case #5: Measure
Measure 3 =
    CALCULATE(
        [No. of Sales],
        FILTER(                     // Row Context -> CALCULATE -> CT
            Winesales,
            [Total Cases] > 350))

Tutte e cinque le espressioni condividono i seguenti tre attributi:

  1. utilizzano tutte la funzione CALCULATE. Tutte le Measure richiamano implicitamente CALCULATE, anche se non chiamano esplicitamente la funzione. Pertanto, Column 2 e Measure 1, che fanno entrambe riferimento alla misura Total Cases, richiamano entrambe implicitamente CALCULATE. Le altre espressioni utilizzano CALCULATE in modo esplicito.

  2. tutte iterano le rispettive tabelle, creando un Row Context. Tutte le colonne calcolate iterano le tabelle. Le Measure rimanenti utilizzano le funzioni AVERAGEX e FILTER, che sono funzioni di iterazione.

  3. tutte richiamano la Context transition. È qui che il Row Context viene trasformato in un Filter Context tramite una chiamata implicita o esplicita alla funzione CALCULATE.

Context Transition VS Propagazione del Row Context

È importante distinguere il processo di Context Transition dalla propagazione del Row Context ottenuta tramite le funzioni DAX RELATED e RELATEDTABLE. Anche se entrambi i meccanismi coinvolgono il Row Context, operano in modo completamente diverso e servono a scopi differenti.

Come abbiamo già detto, la Context Transition è il processo mediante il quale un Row Context viene trasformato in un Filter Context equivalente, tipicamente quando si utilizza CALCULATE (in modo esplicito), o quando una Misura viene valutata in presenza di un Row Context (in modo implicito). Questo meccanismo influisce direttamente sulla valutazione delle Measure, poiché converte i valori della riga corrente in filtri attivi sul modello.

Al contrario, l’uso delle funzioni RELATED e RELATEDTABLE non creano alcun Filter Context. Queste funzioni si limitano a propagare il Row Context lungo le relazioni del modello per permettere l’accesso ai dati della tabella correlata. RELATED recupera un singolo valore dalla tabella collegata sul lato “uno” della relazione, mentre RELATEDTABLE restituisce l’insieme delle righe correlate sul lato “molti”. Ad esempio, nella Misura seguente accade che:

1// Measure
2Total Sales =
3    SUMX(
4        // table
5        Winesales,
6
7        // expression
8        Winesales[CASES SOLD] * RELATED(Wines[PRICE PER CASE])
9    )
  • la funzione di iterazione SUMX (linea 3) scorre una riga alla volta della tabella Winesales, generando un Row Context per ciascuna riga processata;

  • a linea 8, quando la formula tenta di ottenere il valore di Wines[PRICE PER CASE], la funzione RELATED utilizza la relazione tra le tabelle Winesales e Wines per trasferire il Row Context dalla tabella dei fatti alla tabella dimensionale Wines. Questo permette alla formula di recuperare il prezzo corretto per quella specifica riga di Winesales.

In questo caso, non avviene nessuna Context Transition, poichè non viè nessuna chiama esplicita od implicita alla funzione CALCULATE o CALCULATETABLE.