Schéma en étoile, de bout en bout
Résumé
Un schéma en étoile met au centre une table de faits (les mesures d’un événement) entourée de dimensions (le contexte : date, produit, client). On le construit en quatre décisions prises dans l’ordre : le processus métier, le grain, les dimensions, les faits. Cette note construit un petit schéma complet, exécuté sur DuckDB, depuis les tables de la note de normalisation jusqu’aux requêtes d’analyse.
Prérequis
Normalisation 2NF et 3NF (le point de départ transactionnel) et Inmon, Kimball et Data Vault (l’approche dimensionnelle). Le vocabulaire est dans le Glossaire data engineering.
Les quatre décisions
La méthode de Kimball se résume en quatre décisions, prises avec le métier en tenant compte des réalités des données sources :1
- Choisir le processus métier : ici, la prise de commande.
- Déclarer le grain : ce que représente exactement une ligne de la table de faits.
- Identifier les dimensions : le contexte descriptif.
- Identifier les faits : les mesures numériques.
Le grain d’abord
C’est la décision qui conditionne les autres : dire ce que représente une ligne de la table de faits, et s’y tenir. Toute dimension ou mesure envisagée doit être compatible avec ce choix. Kimball recommande de partir du grain atomique, le niveau le plus détaillé que le processus enregistre, car il répond aussi aux questions qu’on n’avait pas prévues ; un grain agrégé accélère des requêtes connues mais suppose qu’on sait déjà quelles questions seront posées. Deux grains différents appellent deux tables de faits.2
Ici : une ligne par ligne de commande (une commande, un produit).
Le schéma
erDiagram FCT_LIGNE_COMMANDE }o--|| DIM_DATE : "date_key" FCT_LIGNE_COMMANDE }o--|| DIM_PRODUIT : "produit_key" FCT_LIGNE_COMMANDE }o--|| DIM_CLIENT : "client_key"
Au grain le plus fin, une ligne de faits correspond à un événement mesuré dans le monde réel, ce qui rend sa conception indépendante des rapports qu’on en tirera. Elle porte les mesures numériques, une clé étrangère par dimension, et parfois une dimension dégénérée (ici le numéro de commande) ou des horodatages. C’est elle que les requêtes agrègent.3
Les dimensions
create table dim_date (
date_key int primary key, -- 20261005 : clé « intelligente », exception usuelle pour la date
date_jour date, annee int, mois int, nom_mois text);
create table dim_produit (
produit_key int primary key, -- clé de substitution
produit_id text, -- clé naturelle de la source
nom text, categorie text);
create table dim_client (
client_key int primary key,
client_id int, nom text, ville text);Chaque dimension a une clé de substitution (*_key), distincte de la clé de la source : Kimball recommande que chaque jointure entre faits et dimensions repose sur ces clés, qui généralisent la clé naturelle et protègent des changements de la source.4 La dimension dim_client est celle où un type de SCD s’applique : Slowly Changing Dimensions, types 0 à 3.
La dimension de date se génère plutôt qu’elle ne se charge. Sur DuckDB :
insert into dim_date
select cast(strftime(d, '%Y%m%d') as int), d, year(d), month(d), strftime(d, '%Y-%m')
from (select unnest(generate_series(date '2026-01-01', date '2026-03-31', interval 1 day))::date as d);(Sur BigQuery, UNNEST(GENERATE_DATE_ARRAY(DATE '2026-01-01', DATE '2026-03-31')) joue ce rôle.) Elle porte tous les attributs de calendrier utiles aux analyses (année, mois, jour de semaine, jour férié) pour ne pas les recalculer dans chaque requête.
Les faits
create table fct_ligne_commande (
commande_id int, -- dimension dégénérée
ligne int,
date_key int, produit_key int, client_key int,
quantite int, -- fait additif
montant decimal(10,2), -- fait additif
primary key (commande_id, ligne));Les mesures quantite et montant sont additives : on peut les sommer sur n’importe quelle dimension. Un prix unitaire, lui, ne se somme pas : le stocker comme fait demande de réfléchir à son agrégation. Le prix payé est conservé dans la ligne de fait, pas lu depuis la dimension produit, pour garder l’historique des ventes (voir l’avertissement de Normalisation 2NF et 3NF).
Les données d’exemple
| commande_id | ligne | date_key | produit | client | quantité | montant |
|---|---|---|---|---|---|---|
| 10 | 1 | 20260105 | Clavier | Dupont (Lyon) | 2 | 50,00 |
| 10 | 2 | 20260105 | Souris | Dupont (Lyon) | 1 | 15,00 |
| 11 | 1 | 20260210 | Écran | Dupont (Lyon) | 1 | 250,00 |
| 12 | 1 | 20260120 | Clavier | Martin (Toulouse) | 5 | 125,00 |
| 13 | 1 | 20260202 | Souris | Durand (Inconnue) | 2 | 30,00 |
Le client Durand n’avait pas de ville dans la source : la dimension porte la valeur « Inconnue » plutôt qu’un NULL, pour que les regroupements affichent une ligne lisible.
Interroger l’étoile
Toute requête suit le même patron : joindre les faits aux dimensions utiles, filtrer et regrouper sur leurs attributs, agréger les mesures.
select d.nom_mois, p.categorie, sum(f.montant) as ca, sum(f.quantite) as qte
from fct_ligne_commande f
join dim_date d using (date_key)
join dim_produit p using (produit_key)
group by d.nom_mois, p.categorie
order by 1, 2;| nom_mois | categorie | ca | qte |
|---|---|---|---|
| 2026-01 | Périphériques | 190,00 | 8 |
| 2026-02 | Affichage | 250,00 | 1 |
| 2026-02 | Périphériques | 30,00 | 2 |
select cl.ville, sum(f.montant) as ca
from fct_ligne_commande f join dim_client cl using (client_key)
group by 1 order by 2 desc;| ville | ca |
|---|---|
| Lyon | 315,00 |
| Toulouse | 125,00 |
| Inconnue | 30,00 |
Changer d’axe d’analyse (par ville plutôt que par catégorie) ne demande que de changer la dimension jointe ; la table de faits ne change pas.
Contrôles
Deux requêtes à garder dans les tests du modèle (voir Observabilité des données) :
-- faits orphelins : une clé qui ne pointe vers aucune dimension
select count(*) from fct_ligne_commande f
left join dim_produit p using (produit_key) where p.produit_key is null;
-- respect du grain : une seule ligne par (commande, ligne)
select commande_id, ligne, count(*) from fct_ligne_commande
group by 1, 2 having count(*) > 1;Sur les données d’exemple, la première renvoie 0 et la seconde aucune ligne. Sur BigQuery, les clés primaires et étrangères déclarées ne sont pas appliquées : ces requêtes (ou des tests dbt unique et relationships) sont le seul contrôle réel.
Choix et limites
- Plus de jointures qu’une table plate : c’est le prix de la clarté ; les moteurs colonnaires gèrent bien ces jointures (OLTP vs OLAP et stockage en colonnes).
- Schéma en flocon : normaliser une dimension en plusieurs tables économise de la place mais complique les requêtes ; c’est une technique décrite par Kimball, souvent déconseillée en pratique pour cette raison.
- Plusieurs processus, plusieurs étoiles : chaque processus a sa table de faits ; elles partagent des dimensions conformées.
- Une étoile construite dans dbt : les dimensions et faits sont des modèles
dim_etfct_de la couche marts : dbt — Fondamentaux.
Notes liées
- Normalisation 2NF et 3NF : le modèle transactionnel d’où l’on part.
- Inmon, Kimball et Data Vault : le contexte des approches de modélisation.
- Slowly Changing Dimensions, types 0 à 3 : faire évoluer une dimension.
- OLTP vs OLAP et stockage en colonnes : pourquoi ce modèle convient à l’analytique.
- dbt — Fondamentaux : construire dimensions et faits.
- Glossaire data engineering : grain, clé de substitution, dimension conforme.
- MOC Data Engineering : carte du vault.
Notes de bas de page
-
Kimball Group, Four-Step Dimensional Design Process, consulté le 24 octobre 2026. ↩
-
Kimball Group, Grain, consulté le 24 octobre 2026. ↩
-
Kimball Group, Fact Table Structure, consulté le 24 octobre 2026. ↩
-
Ralph Kimball, Surrogate Keys (design tip, 2 mai 1998), consulté le 24 octobre 2026. ↩