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.