Il modello semantico è il punto in cui le scelte di modellazione diventano performance percepita. In Microsoft Fabric l’architettura medallion porta i dati fino al layer gold, ed è lì — non nel modello — che si costruisce lo star schema definitivo.
Perché lo star schema vince
Il motore VertiPaq (e Direct Lake, che legge direttamente i file Delta di OneLake) è ottimizzato per un pattern preciso: poche tabelle dimensione piccole che filtrano una tabella fatti grande.
| Design | Effetto sul motore | Effetto sulle misure |
|---|---|---|
| Star schema | Un solo hop di filtro fatti↔dimensione, join scan ottimizzato | Misure semplici, SUM + filtri naturali |
| Snowflake | Catena di propagazione multi-hop, più join a runtime | DAX più fragile, serve risalire più livelli |
| Tabella piatta | Colonne di dimensione ripetute su milioni di righe: compressione peggiore, memoria alta | DISTINCTCOUNT costosi, nessun contesto pulito per slicer |
La tabella piatta sembra semplice ma esplode la cardinalità delle colonne testuali: la stessa descrizione prodotto ripetuta 50 milioni di volte comprime peggio di una dimensione da 5.000 righe. Denormalizza le dimensioni nel gold: unisci Category e Subcategory dentro DimProduct con una vista o una tabella Delta nel Warehouse/Lakehouse, invece di lasciare lo snowflake al modello. Il costo si paga una volta in pipeline, non a ogni query.
Regole pratiche per il gold layer
- Ogni dimensione ha una surrogate key intera; evita chiavi stringa o composite (le relazioni Power BI sono su colonna singola).
- Le colonne testuali stanno nelle dimensioni, le colonne numeriche additive nei fatti.
- Nascondi le colonne chiave e le colonne fatti grezze: l’utente deve vedere misure, non campi sommabili per errore.
- In Direct Lake colonne calcolate e tabelle calcolate DAX non sono supportate: tutto ciò che serve va materializzato a monte (inclusa la date table).
Cardinalità e direzione del filtro
La relazione canonica è uno-a-molti dalla dimensione ai fatti, con cross-filter singolo: il filtro scende dal lato “uno” al lato “molti”. È la configurazione predefinita e quella corretta nel 99% dei casi.
Il bidirezionale propaga anche verso l’alto, e introduce due problemi:
- Ambiguità: con più percorsi possibili tra due tabelle il motore non sa quale usare; Power BI blocca l’attivazione o genera risultati non deterministici.
- Performance: ogni query deve materializzare il filtro inverso sulla dimensione.
Alternative da preferire:
CROSSFILTERdentroCALCULATEper attivare il bidirezionale solo nella misura che ne ha bisogno:Clienti attivi = CALCULATE(DISTINCTCOUNT(DimCustomer[CustomerKey]), CROSSFILTER(FactSales[CustomerKey], DimCustomer[CustomerKey], BOTH))- Bridge table per relazioni molti-a-molti reali (es. cliente ↔ account): una tabella ponte con le coppie distinte e due relazioni uno-a-molti, invece di una relazione many-to-many diretta.
- Row-level security: attenzione, il bidirezionale combinato con RLS espande la superficie di filtro e può esporre righe non previste.
Role-playing dimension
Quando FactSales ha OrderDateKey, ShipDateKey e DueDateKey, non duplicare la date table tre volte: crea una relazione attiva (tipicamente su OrderDateKey) e le altre come relazioni inattive (linea tratteggiata). Poi attivale per misura:
Vendite spedite = CALCULATE([Vendite], USERELATIONSHIP(FactSales[ShipDateKey], DimDate[DateKey]))
USERELATIONSHIP funziona solo dentro CALCULATE/CALCULATETABLE ed è incompatibile con RLS applicata a quella tabella. La duplicazione fisica della dimensione resta valida solo quando l’utente deve incrociare due date nello stesso visual.
La date table fatta bene
Requisiti per Mark as date table: colonna di tipo date/datetime, valori univoci, nessun blank, e un intervallo contiguo che copra anni interi (1 gennaio – 31 dicembre) per ogni anno presente nei fatti. Senza questi requisiti DATESYTD, SAMEPERIODLASTYEAR e le altre funzioni di time intelligence restituiscono risultati errati o vuoti.
Disattiva sempre Auto date/time: crea tabelle date nascoste per ogni colonna data, gonfiando il modello. In Direct Lake la date table va generata nel gold layer (SQL o notebook) con colonne già pronte: Year, QuarterName, MonthName, MonthNumber — e ordina le colonne testuali con Sort by column sulla corrispondente numerica.
Trappole tipiche d’esame
- Scenario: le misure con
TOTALYTDrestituiscono blank su alcuni anni → Risposta: la date table non è contigua/non è marcata come date table; estendi il calendario ad anni completi e usa Mark as date table. - Scenario: serve filtrare la dimensione prodotto in base ai soli prodotti venduti, ma il modello ha già più percorsi → Risposta: non impostare il bidirezionale sulla relazione; usa
CROSSFILTER(..., BOTH)nella singola misura. - Scenario: il fatto ha tre chiavi data verso
DimDate→ Risposta: una relazione attiva + relazioni inattive attivate conUSERELATIONSHIPinCALCULATE, non tre copie della date table. - Scenario: modello Direct Lake, chiedono di aggiungere una colonna calcolata
FullNamealla dimensione → Risposta: non supportato; aggiungila come colonna nella tabella Delta del gold layer (o passa a Import). - Scenario: report lento con un’unica tabella piatta da 200 milioni di righe → Risposta: refactor a star schema nel gold, non aggiunta di indici o aggregazioni improvvisate.
- Scenario: relazione molti-a-molti tra clienti e conti con doppio conteggio degli importi → Risposta: introduci una bridge table con due relazioni uno-a-molti, evitando la relazione many-to-many diretta.