Uno degli errori più comuni in DAX, non riguarda la sintassi, ma il livello a cui stiamo realmente facendo il calcolo. Molte misure apparentemente corrette producono risultati fuorvianti semplicemente perché l’aggregazione avviene su un’unità logica sbagliata.
Gli iteratori DAX non sono semplici funzioni di aggregazione avanzate, ma meccanismi di calcolo che definiscono come e su cosa vengono combinati i dati.
Da un punto di vista concettuale, iterare riga per riga significa aver compreso cosa sia l’unità logica di aggregazione, per scrivere misure corrette semanticamente, manutenibili e performanti.
Con gli iteratori DAX SUMX, AVERAGEX, MINX e MAXX, la domanda giusta è:
“Su quale unità sto calcolando questo valore?”
Dove eravamo rimasti: SUM vs SUMX
Con questo esempio facciamo un breve riassunto delle maggiori differenze tra un funzione di aggregazione che agisce su una colonna dati e una funzione iteratrice, che valuta un’espressione DAX riga per riga e ne aggrega i risultati parziali.
SUM - Aggregazione diretta
SUM aggrega una colonna fisica già pronta.
- non introduce contesto di riga
- è più efficiente
- va usata quando il valore esiste già (colonna del dataset)
Totale Vendite := SUM( Vendite[Importo] )
SUMX - Calcolo + aggregazione
SUMX calcola un’espressione per unità logica e poi aggrega i risultati.
- introduce contesto di riga
- valuta l’espressione prima dell’aggregazione
- è indispensabile quando il valore non esiste come colonna dati
Totale Vendite :=
SUMX(
Vendite,
Vendite[Quantità] * Vendite[Prezzo Unità]
)
Differenza chiave
- SUM aggrega dati già calcolati
- SUMX calcola e aggrega in due fasi distinte
Iteratori DAX e tabelle virtuali: controllare l’unità logica di iterazione
In molti scenari reali, l’unità logica su cui vogliamo iterare non coincide con una tabella fisica del modello, ma con un insieme di valori determinato dal contesto.
In questi casi, DAX consente di definire tabelle virtuali tramite funzioni come VALUES, ALL, ALLSELECTED o SUMMARIZE, che diventano a tutti gli effetti l’insieme di righe (unità logiche) su cui l’iteratore lavora.
Unità di aggregazione e significato del risultato
Supponiamo una fact table Vendite e supponiamo di voler calcolare l’importo medio per vendita.
Caso 1 – Una riga per ogni transazione
Se la tabella contiene una riga per ogni vendita, allora l’espressione:
AVERAGEX(
Vendite,
Vendite[Quantità] * Vendite[Prezzo Unità]
)
restituisce correttamente l’importo medio per vendita.
Caso 2 – Più righe per la stessa transazione
Se invece la tabella contiene più righe per la stessa vendita (ad esempio nello scontrino, dove ogni prodotto è registrato su una riga) la formula precendente non è più valida.
Perché:
- l’iterazione avverrebbe sull’unità singolo prodotto venduto
- il risultato sarebbe una media per riga, cioè per prodotto e non per transazione di vendita
In questo scenario, se l’obiettivo è ottenere l’importo medio per vendita, allora l’iterazione deve avvenire su una diversa unità logica, ad esempio creando una tabella virtuale che raggruppi per Vend.ID.
AVERAGEX(
VALUES ( Vendite[Vend.ID] ),
CALCULATE ( Vendite, Vendite[Quantità] * Vendite[Prezzo Unità] )
)
Cosa succede realmente
- VALUES(<Table>)
- restituisce una tabella virtuale
- contiene una riga per ogni transazione, ovvero valore distinto di ID
- AVERAGEX
- itera su ogni valore di ID, della tabella virtuale
- calcola l’importo totale per ogni transazione
- restituisce il valore medio degli importi calcolati
Concetti chiave
- La riga di iterazione non corrisponde alla singola riga della tabella fisica
- VALUES(‘Vendite[Vend.ID]’) definisce l’unità logica
- la media è calcolata per unità logica
L’iteratore non “capisce” il contesto di utilizzo! itera sull’unità che gli fornisci, quindi è compito dell’analista scegliere correttemante l’unità di aggregazione.
Pattern comune su tabella virtuale
Scenario: massimo/minimo valore in un elemento visivo
In un elemento visivo ci sono:
- la dimensione — (ad esempio: Prodotto, Categoria, Mese, ecc.)
- la misura [Measure] calcolata per ogni elemento di —
L’obiettivo è evidenziare visivamente il valore massimo della misura tra gli elementi visibili nel visual, rispettando slicer e filtri esterni.
Soluzione con tabella virtuale
Color Max Sales Amount Per Product =
VAR _Max =
MAXX(
ALLSELECTED( ProductSubCategory[ProductSubcategory] ),
[Sales Amount]
)
VAR _SA = [Sales Amount]
RETURN
IF ( _SA = _Max, “#3D57A3”, “#3FB9E0”)
Cosa succede realmente
- ALLSELECTED(‘ ProductSubCategory[ProductSubcategory]’)
- restituisce una tabella virtuale
- contiene una riga per ogni valore distinto della sottocategoria di prodotto attualmente visibile nel visual
- rispetta slicer, filtri di pagina e report, ma ignora il contesto di filtro del grafico
2. MAXX
- itera su ProductSubCategory[ProductSubcategory]
- valuta la misura l’importo per ciascuna sottocategoria di prodotto valida
- restituisce il valore massimo tra quelli calcolati
Decision tree: devo usare un iteratore DAX?
Lo schema sottostante serve a guidarvi nella scelta o no dell’iteratore nel calcolo e nell’individuazione della corretta unità logica.
Una scelta progettuale più consapevole: come non comettere errori
Una corretta modellazione del dato parte dall’identificare la minima granularità necessaria alle analisi.
Per questo motivo molto spesso si lavora in aggregazione, e quando è necessario definire delle unità logiche diverse da quelle del dataset, occorre essere consapevoli del funzionamento degli iteratori, per non incappare in risultati inattesi.
Scopri tutte le nostre soluzioni Power BI
Al tuo fianco dalla consulenza alla formazione a soluzioni di data visualization.
Visualitics Team
Questo articolo è stato scritto e redatto da uno dei nostri consulenti.
Condividi ora sui tuoi canali social o via email: