The Problem with the Flat Table
Many exports look like our ventes_plat.csv file: one big table where each sales row repeats the store name, its region, the product name, its category… It works for a small file, but quickly causes problems:
- redundancy: “Auvergne-Rhône-Alpes” is repeated on every Lyon sale;
- inconsistencies: if one row writes “Lyon” and another “LYON”, you get two stores;
- difficulty adding other facts: how do you link monthly targets by store to a table of detailed sales?
- more complex calculations in DAX, and poorer performance on large volumes.
The solution, coming from dimensional modeling (popularized by Ralph Kimball), is the star schema.
Facts and Dimensions
A fact table records events that you measure: a sale, an order, a call, an accounting entry. It contains:
- keys to the dimensions (
CodeMagasin,CodeProduit,Date); - numeric values to aggregate (
Quantite,PrixUnitaire,Montant); - many rows, and it grows over time.
A dimension table describes the context: who, what, where, when. It contains:
- a unique key (one row per store, per product, per day);
- descriptive attributes for filtering and grouping (
Region,Categorie,Mois); - relatively few rows.
In our file: FaitVentes (NumVente, Date, DateLivraison, CodeMagasin, CodeProduit, Quantite, PrixUnitaire), DimMagasin (CodeMagasin, Magasin, Region), DimProduit (CodeProduit, Produit, Categorie) and a date table.
The Grain
The grain answers the question: “what does one row of the fact table represent?”. Here: one row = one product sold in a store during a sale. Targets, on the other hand, are defined per store and per month: that is another grain, so another fact table.
Always declare the grain before building a model: mixing two grains in the same table leads to wrong totals.
The Star
DimProduit
|
DimMagasin — FaitVentes — Dates
The dimensions surround the fact table and are linked directly to it by one-to-many relationships (one dimension row, several fact rows). Filters “flow down” from the dimensions to the facts: choosing the region Hauts-de-France filters the Lille sales.
If you linked DimProduit to a separate DimCategorie table, you would get a snowflake. Power BI supports it, but it’s generally preferable to flatten the category into DimProduit: fewer relationships, simpler filters, a more readable model.
With a good model, measures stay simple:
Total ventes = SUM ( FaitVentes[Montant] )
and work whatever the chosen axis of analysis (store, product, month).
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
Analyze the flat table
Course dataset: in Notepad, create
C:\Formation\ventes_plat.csv(UTF-8):NumVente,Date,DateLivraison,CodeMagasin,Magasin,Region,CodeProduit,Produit,Categorie,Quantite,PrixUnitaire V001,2026-01-03,2026-01-05,M1,Lyon,Auvergne-Rhône-Alpes,P01,Jus d'orange,Boissons,10,3 V002,2026-01-03,2026-01-06,M2,Lille,Hauts-de-France,P02,Pates,Epicerie,20,2 V003,2026-01-10,2026-01-12,M3,Nantes,Pays de la Loire,P03,Yaourt,Frais,30,1 V004,2026-01-17,2026-01-19,M1,Lyon,Auvergne-Rhône-Alpes,P04,Riz,Epicerie,15,2 V005,2026-01-24,2026-01-27,M2,Lille,Hauts-de-France,P05,Eau,Boissons,50,1 V006,2026-02-02,2026-02-04,M3,Nantes,Pays de la Loire,P01,Jus d'orange,Boissons,12,3 V007,2026-02-07,2026-02-09,M1,Lyon,Auvergne-Rhône-Alpes,P06,Fromage,Frais,8,6 V008,2026-02-14,2026-02-16,M2,Lille,Hauts-de-France,P03,Yaourt,Frais,25,1 V009,2026-02-21,2026-02-24,M3,Nantes,Pays de la Loire,P02,Pates,Epicerie,18,2 V010,2026-02-28,2026-03-03,M1,Lyon,Auvergne-Rhône-Alpes,P05,Eau,Boissons,40,1 V011,2026-03-06,2026-03-09,M2,Lille,Hauts-de-France,P04,Riz,Epicerie,10,2 V012,2026-03-13,2026-03-16,M3,Nantes,Pays de la Loire,P06,Fromage,Frais,5,6- Import the file into Power BI Desktop (table VentesPlat).
- In Table view, count the number of distinct values of
CodeMagasinandCodeProduit(select the column: the status bar or the Power Query column profile shows it). - Classify each of the 11 columns as: key, dimension attribute or fact value.
- Write the grain of the table in one sentence.
- The number of distinct stores is 3 and of distinct products 6
- Region, Magasin, Produit, Categorie are classified as dimension attributes
- Quantite and PrixUnitaire are classified as fact values
- The grain mentions sale, product and store
Fact or dimension?
Which of these columns belongs in a fact table?
- Region name
- Product category
- Quantity sold
- Month name
Different grains
You have daily sales by product and monthly targets by store. What do you do?
- Put them in the same table
- Create two fact tables linked to the common dimensions
- Delete the targets
- Duplicate the targets on every sale
End-of-lesson quiz
What does a dimension table describe?
- The measured events
- The context: who, what, where, when
- The DAX measures
In a star schema, how are the dimensions linked?
- Directly to the fact table
- To each other in a chain
- They are not linked
What is the grain?
- The number of columns
- What one row of the fact table represents
- The size of the file
The rest of the course
- Building Fact and Dimension Tables with Power Query20 min
- Relationships: Cardinality, Filter Direction, Active Relationships20 min
- The Date Table20 min
- Best Practices: Multiple Facts, Readability and Storage Modes20 min