La time intelligence è il cuore di quasi ogni report analitico: confronti anno-su-anno, valori cumulati year-to-date, medie mobili. In DAX questa famiglia di funzioni non “capisce” il tempo da sola: si appoggia a una date table dedicata, marcata e continua. Comprendere questa dipendenza è ciò che distingue una risposta corretta da un risultato silenziosamente sbagliato.
La date table: fondamento non negoziabile
Le funzioni di time intelligence operano riscrivendo il filtro sulla colonna data. Perché funzionino servono tre condizioni:
- una tabella calendario separata dalla fact table, collegata a essa da una relazione sulla colonna data;
- una colonna di tipo Date (o DateTime) continua, senza buchi, che copra l’intero intervallo dei dati, tipicamente dal 1° gennaio del primo anno al 31 dicembre dell’ultimo;
- la tabella marcata come date table con Mark as date table, che indica al motore quale colonna usare per il “contesto tempo” e rimuove i filtri residui su di essa.
Puoi generarla in DAX con CALENDAR o CALENDARAUTO, oppure con una query M. In tutti gli scenari d’esame, le misure vanno filtrate tramite la dimensione calendario collegata, mai tramite la colonna data della fact table.
Costruire YTD e valori cumulati
Per il totale progressivo dall’inizio dell’anno hai due strade equivalenti:
TOTALYTD( [Sales], 'Date'[Date] )— scorciatoia che somma internamente da inizio anno alla data corrente;CALCULATE( [Sales], DATESYTD( 'Date'[Date] ) )— più esplicita: DATESYTD restituisce l’insieme di date da inizio anno fino al contesto corrente, e CALCULATE lo applica come filtro.
Entrambe accettano un parametro opzionale di fine anno fiscale (es. "06-30") per esercizi non solari — dettaglio frequente negli scenari d’esame.
Confronti anno-su-anno
Per allineare il periodo corrente allo stesso periodo dell’anno precedente:
- SAMEPERIODLASTYEAR sposta il contesto date indietro esattamente di un anno:
CALCULATE( [Sales], SAMEPERIODLASTYEAR('Date'[Date]) ); - DATEADD è la versione generalizzata:
DATEADD('Date'[Date], -1, YEAR)permette qualsiasi offset (-1, MONTH,-1, QUARTER, ecc.), quindi è la scelta giusta quando lo shift non è annuale.
La variazione percentuale YoY si costruisce dividendo la differenza tra misura corrente e misura shiftata per quest’ultima, gestendo il caso di denominatore vuoto con DIVIDE.
PARALLELPERIOD e medie mobili
- PARALLELPERIOD sposta e espande al granulo indicato: restituisce l’intero periodo parallelo (es. l’intero anno precedente) indipendentemente dal contesto giornaliero, utile per confronti con un aggregato pieno.
- DATESINPERIOD genera un intervallo a partire da una data per un numero di intervalli: è lo strumento per la media mobile. Esempio a 3 mesi:
Media Mobile 3M =
CALCULATE(
AVERAGEX( VALUES('Date'[Date]), [Sales] ),
DATESINPERIOD('Date'[Date], MAX('Date'[Date]), -3, MONTH)
)
DATESINPERIOD è preferibile a un intervallo hard-coded perché si adatta dinamicamente al contesto e ai bordi del calendario.
Scegliere la funzione giusta
- shift fisso di un anno → SAMEPERIODLASTYEAR;
- shift parametrico di mesi/trimestri/anni → DATEADD;
- confronto con un aggregato di periodo pieno → PARALLELPERIOD;
- finestra scorrevole per medie mobili → DATESINPERIOD;
- cumulato da inizio anno → TOTALYTD o DATESYTD dentro CALCULATE.
Trappole tipiche d’esame
- Scenario: TOTALYTD restituisce valori piatti o palesemente errati → la date table non è marcata con Mark as date table, oppure la colonna data non è continua (ha buchi). Risposta giusta: creare una tabella calendario continua e marcarla.
- Scenario: SAMEPERIODLASTYEAR mostra valori mancanti per alcuni giorni → la colonna calendario non copre l’anno precedente completo. Risposta giusta: estendere l’intervallo con CALENDAR/CALENDARAUTO a coprire tutti gli anni.
- Scenario: la misura filtra su
Sales[OrderDate]invece che su'Date'[Date]→ time intelligence “rotta” perché lavora sulla fact table. Risposta giusta: filtrare sempre sulla dimensione calendario collegata. - Scenario: serve confrontare col mese precedente, non con l’anno → non usare SAMEPERIODLASTYEAR ma DATEADD(‘Date’[Date], -1, MONTH).
- Scenario: media mobile 3 mesi con intervallo scritto a mano → soluzione fragile; la risposta corretta usa DATESINPERIOD con
MAX('Date'[Date])e offset negativo.