Guida data engineering

Dimensional Modeling Guide

La differenza tra un data warehouse che gli analisti amano e uno che evitano? Il dimensional modeling. Impara a costruire star schema intuitivi e velocissimi.

25 min di letturaPer analisti e data engineerEsempi SQL inclusi

1. Cos’è il dimensional modeling?

Il dimensional modeling è una tecnica di progettazione dei data warehouse che mette semplicità di query e performance davanti all’efficienza di storage. Creato da Ralph Kimball, resta lo standard perché modella i dati come li vede il business.

Perché non usare solo 3NF?

La Third Normal Form è ottima per l’OLTP: minima ridondanza, niente anomalie di update. Per l’analytics è pessima: query con 15+ join, performance bassa e modello incomprensibile per gli analisti.

Principio chiave: separare il “cosa è successo” dal “contesto”

Tabelle dei fatti

Salvano eventi e misure: ordini, clic, pagamenti. Contengono metriche aggregabili (SUM, COUNT, AVG).

Tabelle delle dimensioni

Salvano il contesto descrittivo: chi (clienti), cosa (prodotti), quando (date), dove (sedi). Abilitano filtri e raggruppamenti.

Filosofia Kimball

"Il data warehouse vale quanto la BI che abilita." I modelli dimensionali sono progettati per le persone, non per le macchine. Se un analista non può scrivere query senza aiuto, il modello ha fallito.

2. Fatti vs dimensioni

I mattoni fondamentali del dimensional modeling. Se questi sono corretti, il resto segue.

Tabelle dei fatti

  • Registrano eventi/transazioni
  • Contengono misure numeriche (importo, quantità, durata)
  • Hanno chiavi esterne verso le dimensioni
  • Alte e strette (molte righe, poche colonne)
  • Grain = una riga per evento

Esempi:

fct_orders, fct_page_views, fct_payments

Tabelle delle dimensioni

  • Memorizzano attributi descrittivi
  • Contengono campi testuali per filtri/raggruppamenti
  • Hanno una surrogate key come chiave primaria
  • Basse e larghe (poche righe, molte colonne)
  • Grain = una riga per entità

Esempi:

dim_customers, dim_products, dim_date

DomandaSe la risposta è...È una...
Puoi fare SUM/COUNT/AVG?Fatto
Lo useresti in GROUP BY o FILTER?Dimensione
Descrive un’entità?Dimensione
Registra un evento/una transazione?Fatto

Esempio: ordine e-commerce

-- Tabella dei fatti: una riga per linea d'ordine
CREATE TABLE fct_order_lines (
    order_line_sk       BIGINT PRIMARY KEY,    -- Surrogate key
    order_id            VARCHAR(50),           -- Natural key (dim. degenere)
    customer_sk         BIGINT REFERENCES dim_customers,
    product_sk          BIGINT REFERENCES dim_products,
    date_sk             INT REFERENCES dim_date,
    -- Misure (aggregabili)
    quantity            INT,
    unit_price          DECIMAL(10,2),
    discount_amount     DECIMAL(10,2),
    line_total          DECIMAL(10,2)
);

-- Tabella dimensione: una riga per cliente
CREATE TABLE dim_customers (
    customer_sk         BIGINT PRIMARY KEY,    -- Surrogate key
    customer_id         VARCHAR(50),           -- Natural key
    customer_name       VARCHAR(255),
    email               VARCHAR(255),
    segment             VARCHAR(50),           -- 'Enterprise', 'SMB', 'Consumer'
    acquisition_channel VARCHAR(50),
    created_at          TIMESTAMP
);

3. Design dello star schema

Uno star schema mette la tabella dei fatti al centro, circondata dalle dimensioni. Il nome viene dalla forma: fatto al centro, dimensioni che irradiano come punte di una stella.

Struttura Star Schema

dim_date

dim_customer

fct_orders

dim_product

dim_store

Tabella dei fatti al centro, dimensioni intorno — join semplici, query intuitive

Star vs Snowflake Schema

Star schema (preferito)

  • • Dimensioni denormalizzate
  • • Meno join = query più veloci
  • • Più facile da capire e interrogare
  • • Leggera ridondanza (ok per analytics)

Snowflake schema

  • • Dimensioni normalizzate
  • • Più join = query più lente
  • • Difficile da usare senza documentazione
  • • Risparmia storage (raramente importante)

Consiglio

Quando usare lo snowflake?

Snowflake solo quando la dimensione è davvero gerarchica E gli analisti interrogano spesso livelli diversi separatamente (prodotto → categoria → reparto). Altrimenti denormalizza tutto nella dimensione.

4. Tipi di dimensioni

Non tutte le dimensioni sono uguali. Conoscere questi pattern aiuta a modellare bene fin dall’inizio.

1

Conformed dimensions

Condivise da più tabelle dei fatti. dim_date, dim_customer usate da fct_orders, fct_page_views, fct_support_tickets. Chiave per coerenza aziendale.

2

Role-playing dimensions

Stessa dimensione usata più volte con significati diversi. dim_date come order_date, ship_date, delivery_date. Usa view o alias.

3

Dimensioni degenerate

Chiavi di dimensione nella tabella dei fatti senza dimensione separata. Numeri ordine, ID fattura, ID transazione. Nessun attributo da salvare altrove.

4

Junk dimensions

Combina flag a bassa cardinalità in un’unica dimensione. Invece di 5 boolean nella tabella dei fatti: dim_order_flags con tutte le combinazioni.

5

Dimensione data

La dimensione più importante. Attributi precalcolati: day_of_week, is_weekend, fiscal_quarter, holiday_flag. Usa sempre surrogate key intere (formato YYYYMMDD).

Esempio: dimensione data

CREATE TABLE dim_date (
    date_sk             INT PRIMARY KEY,       -- formato YYYYMMDD
    date_actual         DATE NOT NULL,
    day_of_week         VARCHAR(10),           -- 'Monday', 'Tuesday', ...
    day_of_week_num     INT,                   -- 1-7
    day_of_month        INT,
    day_of_year         INT,
    week_of_year        INT,
    month_num           INT,
    month_name          VARCHAR(10),
    quarter_num         INT,
    quarter_name        VARCHAR(10),           -- 'Q1', 'Q2', ...
    year_num            INT,
    fiscal_year         INT,
    fiscal_quarter      INT,
    is_weekend          BOOLEAN,
    is_holiday          BOOLEAN,
    holiday_name        VARCHAR(50)
);

-- Uso: filtrare/raggruppare facilmente per ogni attributo data
SELECT 
    d.month_name,
    d.year_num,
    SUM(f.order_total) as revenue
FROM fct_orders f
JOIN dim_date d ON f.order_date_sk = d.date_sk
WHERE d.is_weekend = FALSE
GROUP BY d.month_name, d.year_num;

5. Slowly Changing Dimensions (SCD)

Gli attributi delle dimensioni cambiano nel tempo. Un cliente cambia indirizzo, un prodotto viene riclassificato, un dipendente cambia reparto. Gestirli correttamente è cruciale per la fedeltà storica.

Tipo 0: Fisso

Non cambia mai. Valore originale preservato. Usa per attributi immutabili (data iscrizione, data di nascita).

Esempio: Il canale di acquisizione originario resta anche se il cliente torna da un altro canale.

Tipo 1: Overwrite

Il valore vecchio viene sovrascritto. Nessuna storia. Usa per correzioni o attributi senza valore storico.

Trade-off: Semplice, ma non puoi rispondere a “in che segmento era al momento dell’ordine?”.

Tipo 2: Nuova riga (il più comune)

Crea una nuova riga di dimensione con nuova surrogate key. Traccia con effective_date, expiry_date, flag is_current. Storia completa preservata.

Ideale per: Attributi dove conta la storia. Cambio segmento, indirizzo, pricing tier.

Tipo 3: Nuova colonna

Aggiunge colonne current_value e previous_value. Storia limitata (tipicamente un valore precedente).

Raro: Utile solo per confronti “prima/dopo”. Il tipo 2 è quasi sempre migliore.

Esempio: SCD Tipo 2

-- SCD Tipo 2: il cliente passa da 'SMB' a 'Enterprise'
-- PRIMA: 1 riga
customer_sk | customer_id | segment    | effective_date | expiry_date | is_current
1           | C001        | SMB        | 2023-01-01     | 9999-12-31  | TRUE

-- DOPO: 2 righe (vecchia scade, nuova aggiunta)
customer_sk | customer_id | segment    | effective_date | expiry_date | is_current
1           | C001        | SMB        | 2023-01-01     | 2024-06-15  | FALSE
2           | C001        | Enterprise | 2024-06-15     | 9999-12-31  | TRUE

-- Query storica: quale segmento al momento dell'ordine?
SELECT 
    o.order_id,
    c.segment as segment_at_order_time
FROM fct_orders o
JOIN dim_customers c ON o.customer_sk = c.customer_sk
-- customer_sk nella tabella dei fatti punta alla versione storica corretta

Insidia del Tipo 2

Con il Tipo 2 devi decidere in fase di load quale surrogate key assegnare al fatto. Di solito vuoi la versione "current" al momento dell’evento. Serve un lookup point-in-time nell’ETL.

6. Tipi di tabelle dei fatti

Processi diversi richiedono design diversi della tabella dei fatti. Scegli il tipo in base alla natura degli eventi.

Transaction facts

Una riga per evento al grain più basso. Il tipo più comune. Ordini, clic, pagamenti, login.

Grain: Una riga per linea d’ordine

Periodic snapshot facts

Una riga per intervallo temporale. Cattura lo stato a intervalli regolari. Saldi conto, livelli inventario, snapshot pipeline.

Grain: Una riga per account per giorno

Accumulating snapshot facts

Una riga per istanza di processo, aggiornata man mano che avvengono i milestone. Evasione ordini, richieste di prestito, ticket di supporto.

Grain: Una riga per ordine (aggiornata nel ciclo di vita)

Factless facts

Registrano eventi senza misure—solo chiavi esterne. Presenze studenti, copertura promozioni prodotto.

Uso: Chi era presente? Quali prodotti erano in promo?

Esempio: accumulating snapshot (evasione ordine)

CREATE TABLE fct_order_fulfillment (
    order_sk                BIGINT PRIMARY KEY,
    order_id                VARCHAR(50),
    customer_sk             BIGINT,
    -- Più chiavi data (milestone)
    order_date_sk           INT,
    payment_date_sk         INT,
    ship_date_sk            INT,
    delivery_date_sk        INT,
    -- Lag calcolati
    days_to_payment         INT,
    days_to_ship            INT,
    days_to_delivery        INT,
    -- Misure
    order_total             DECIMAL(10,2),
    current_status          VARCHAR(50)
);

-- La riga si aggiorna quando l'ordine avanza
-- Inizialmente: solo order_date_sk
-- Dopo il pagamento: payment_date_sk compilato, days_to_payment calcolato
-- Dopo la spedizione: ship_date_sk compilato, days_to_ship calcolato
-- Dopo la consegna: delivery_date_sk compilato, days_to_delivery calcolato

7. Best practice (checklist)

Definisci prima il grain

Prima di progettare una tabella dei fatti, scrivi il grain: "Una riga per linea d’ordine" o "Una riga per cliente per giorno". Non mescolare i grain.

Usa surrogate key

Surrogate key intere in tutte le dimensioni. Le natural key restano attributi. Gestiscono bene SCD Tipo 2 e migliorano le join.

Costruisci conformed dimensions

dim_date e dim_customer devono essere condivise tra tutte le tabelle dei fatti. Stesse chiavi, stessi attributi. Abilita analisi cross-process.

Denormalizza le dimensioni

Preferisci star a snowflake. Includi categoria, reparto, regione direttamente nella dimensione. Lo storage è economico, le join no.

Aggiungi una dimensione data

Mai join su date raw. Crea dim_date con attributi precalcolati. Chiavi intere (YYYYMMDD) per il partition pruning.

Gestisci i null con righe di default

Crea righe "Unknown" o "Not Applicable" nelle dimensioni (SK = -1). Niente chiavi esterne null nelle tabelle dei fatti.

Documenta il grain

Ogni tabella dei fatti deve documentare il grain. Previene il double-counting e aiuta gli analisti a scrivere query corrette.

SCD Tipo 2 per attributi critici

Segmento cliente, categoria prodotto, reparto—tutto ciò che cambia e influenza l’analisi dovrebbe essere Tipo 2.

Consiglio

L’"analyst test"

Dopo il design, chiedi a un analista di scrivere 5 query comuni senza documentazione. Se ciascuna richiede meno di 5 minuti, il modello è buono. Se servono chiarimenti o correzioni, semplifica.

8. Domande frequenti

Differenza tra tabella dei fatti e tabella delle dimensioni?

Le tabelle dei fatti memorizzano eventi misurabili (transazioni, clic, ordini) con metriche e chiavi esterne. Le dimensioni memorizzano attributi descrittivi (clienti, categorie prodotto, date) per dare contesto. I fatti rispondono a "quanto", le dimensioni a "chi, cosa, quando, dove, perché".

Star schema vs snowflake schema?

Uno star schema ha dimensioni denormalizzate collegate direttamente alla tabella dei fatti, formando una stella. Uno snowflake normalizza le dimensioni in sotto-dimensioni (prodotto → categoria → reparto). Lo star è preferito perché più semplice e performante.

Cosa sono le Slowly Changing Dimensions?

Le SCD gestiscono cambiamenti di attributo. Tipo 1 sovrascrive (nessuna storia). Tipo 2 aggiunge una nuova riga con date di validità (storia completa). Tipo 3 aggiunge colonne per valori precedenti (storia limitata). Il tipo 2 è lo standard per accuratezza storica.

Surrogate key o natural key?

Usa surrogate key (interi auto-generati) come chiavi primarie nelle dimensioni. Le natural key (ad es. customer_id) come attributi. Le surrogate key sono stabili, performanti e funzionano con SCD Tipo 2. Le natural key possono cambiare e rompere le join.

Visualizza il tuo modello dimensionale

Crea diagrammi star schema chiari con fatti, dimensioni e relazioni. Documenta il modello così gli analisti possono fare query senza chiedere aiuto.