Il DAX è il linguaggio con cui il semantic model risponde alle domande di business. Su Microsoft Fabric le regole non cambiano rispetto a Power BI, ma il modo di archiviazione (Import, DirectQuery, Direct Lake) condiziona pesantemente quali costrutti puoi permetterti.
Contesto di riga e contesto di filtro
Ogni espressione DAX viene valutata dentro due contesti:
- Contesto di riga: esiste dentro colonne calcolate e iteratori (
SUMX,AVERAGEX,FILTER,RANKX). Punta a una riga alla volta e da solo non filtra il modello. - Contesto di filtro: l’insieme dei filtri attivi che arrivano da visual, slicer, righe/colonne di matrice, filtri a livello di pagina e propagazione lungo le relazioni dello star schema.
CALCULATE è l’unica funzione che modifica il contesto di filtro (con la sua variante iterativa CALCULATETABLE). Fa due cose insieme:
- applica i filtri passati come argomenti, sovrascrivendo per default quelli esistenti sulle stesse colonne (
KEEPFILTERSli intersecta invece di sostituirli); - esegue la context transition: converte il contesto di riga in contesto di filtro. È il motivo per cui
CALCULATE(SUM(Sales[Amount]))dentro unSUMXcambia semantica rispetto aSUMpuro.
ALL, REMOVEFILTERS, ALLEXCEPT, USERELATIONSHIP e CROSSFILTER sono modificatori utilizzabili solo dentro CALCULATE.
Misura o colonna calcolata?
| Requisito | Scelta corretta | Perché |
|---|---|---|
| Valore che deve reagire a slicer e livello di aggregazione | Misura | Valutata a query time nel contesto di filtro |
| Attributo da usare come slicer, asse, filtro o relazione | Colonna calcolata (meglio: colonna a monte) | Deve esistere fisicamente nel modello |
| Fact table da milioni di righe | Misura + calcolo upstream | La colonna calcolata si materializza in memoria, spesso con compressione peggiore delle colonne importate |
Su Direct Lake il vincolo è netto: le colonne calcolate e le tabelle calcolate che referenziano tabelle Direct Lake non sono supportate. La logica va spostata a monte — nel Lakehouse (tabella Delta, vista SQL) o nel Warehouse — oppure isolata in una tabella import di un modello composito. Le misure invece funzionano normalmente, perché non richiedono materializzazione.
Date table e time intelligence
Prerequisiti non negoziabili:
- una tabella date dedicata, con date contigue, ad anni interi (dal 1° gennaio al 31 dicembre dell’ultimo anno, o dai confini fiscali corrispondenti);
- relazione dalla date table verso la fact table, cardinalità uno-a-molti, direzione singola;
- la tabella deve essere marcata come date table (Mark as date table) su una colonna di tipo Date/DateTime, univoca e senza valori vuoti.
Pattern da conoscere a memoria:
Sales YTD =
CALCULATE ( [Sales], DATESYTD ( 'Date'[Date] ) )
Sales FY YTD =
CALCULATE ( [Sales], DATESYTD ( 'Date'[Date], "06-30" ) )
Sales PY =
CALCULATE ( [Sales], SAMEPERIODLASTYEAR ( 'Date'[Date] ) )
Sales PY (DATEADD) =
CALCULATE ( [Sales], DATEADD ( 'Date'[Date], -1, YEAR ) )
YoY % =
DIVIDE ( [Sales] - [Sales PY], [Sales PY] )
Running Total =
CALCULATE (
[Sales],
'Date'[Date] <= MAX ( 'Date'[Date] ),
REMOVEFILTERS ( 'Date' )
)
SAMEPERIODLASTYEAR è l’equivalente di DATEADD(..., -1, YEAR); DATEADD è più generale perché accetta anche QUARTER, MONTH, DAY. DIVIDE è sempre preferibile a /: gestisce il denominatore zero o vuoto restituendo BLANK() (o l’alternativa indicata) senza errore.
Per confrontare date d’ordine e di spedizione con due relazioni verso la stessa date table: relazione attiva su una, inattiva sull’altra, attivata con CALCULATE ( [Sales], USERELATIONSHIP ( Sales[ShipDate], 'Date'[Date] ) ).
Diagnosticare le performance
Usa Performance Analyzer in Power BI Desktop per isolare la durata di ogni visual e copiarne la query DAX; poi la DAX query view o DAX Studio per confrontare tempi di formula engine (single-thread, costoso) e storage engine. Un formula engine dominante segnala tipicamente iteratori e FILTER mal scritti.
Trappole tipiche d’esame
- Scenario: le misure YTD restituiscono valori errati o vuoti dopo aver creato la date table → Risposta: mancano date contigue o la tabella non è stata marcata come date table; le funzioni di time intelligence lo richiedono.
- Scenario: la colonna anno/mese è testuale (
"2026-01") eSAMEPERIODLASTYEARfallisce → Risposta: la time intelligence esige una colonna di tipo Date; crea una date table con colonna Date reale, non stringhe. - Scenario:
CALCULATE ( [Sales], FILTER ( Sales, Sales[Amount] > 100 ) )è lentissimo su una fact da 200 milioni di righe → Risposta: sostituisci con un predicato di colonna (Sales[Amount] > 100) oFILTER ( VALUES ( ... ) );FILTERsull’intera tabella forza una scansione riga per riga nel formula engine. - Scenario: serve un attributo di raggruppamento su un modello Direct Lake ma la colonna calcolata non è disponibile → Risposta: calcola la colonna a monte, nella tabella Delta del Lakehouse o in una vista del Warehouse.
- Scenario: la variazione percentuale genera errori di divisione quando l’anno precedente non ha dati → Risposta: usa
DIVIDE, non l’operatore/. - Scenario: un totale mensile risulta uguale al totale generale → Risposta: un
ALL/REMOVEFILTERSsulla date table sta rimuovendo il filtro periodo; usaALLSELECTEDo riporta il filtro conKEEPFILTERS.