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:

  1. applica i filtri passati come argomenti, sovrascrivendo per default quelli esistenti sulle stesse colonne (KEEPFILTERS li intersecta invece di sostituirli);
  2. esegue la context transition: converte il contesto di riga in contesto di filtro. È il motivo per cui CALCULATE(SUM(Sales[Amount])) dentro un SUMX cambia semantica rispetto a SUM puro.

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") e SAMEPERIODLASTYEAR fallisce → 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) o FILTER ( VALUES ( ... ) ); FILTER sull’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/REMOVEFILTERS sulla date table sta rimuovendo il filtro periodo; usa ALLSELECTED o riporta il filtro con KEEPFILTERS.