Skip to content
Back to Blog
Data Engineering October 12, 2026 9 min read

Data Warehouse Explained: Definition, Star Schema and a Retail Example in SQL

A data warehouse is a database built for analysis, not for daily operations. Here is what that means, how a star schema organises it, and a complete retail example in SQL.

Data Warehouse Explained: Definition, Star Schema and a Retail Example in SQL

A data warehouse is a database designed for analysis rather than for day-to-day operations. It gathers data from several sources (ERP, CRM, point of sale, Excel files), cleans it, keeps its history and organises it so that questions like "how much did we sell per region and per month?" can be answered quickly and consistently. Most data warehouses are modelled as a star schema: a central fact table surrounded by dimension tables.

What is a data warehouse?

The classic definition, from Bill Inmon, describes a data warehouse as subject-oriented, integrated, time-variant and non-volatile. In plain words:

  • Subject-oriented — organised around business topics (sales, customers, stock), not around applications.
  • Integrated — the same customer or product has one code and one label, even if each source names it differently.
  • Historised — data is kept over time, so you can compare this year with last year.
  • Non-volatile — data is loaded and read; it is not modified by daily transactions.

The data warehouse sits between the operational systems and the reporting tools (Power BI, Tableau, Looker Studio). It is fed by ETL or ELT pipelines (see ETL vs ELT).

What is the difference between a database and a data warehouse?

An application database and a data warehouse can both run on PostgreSQL or SQL Server. The difference is their purpose and how they are modelled.

CriterionOperational database (OLTP)Data warehouse (OLAP)
PurposeRun the business: record sales, orders, paymentsAnalyse the business: trends, KPIs, comparisons
Typical queryRead or update one rowAggregate millions of rows
ModelNormalised (many small tables)Denormalised star schema
HistoryCurrent state, often overwrittenKept over several years
UsersApplications, operatorsAnalysts, managers, BI tools
UpdatesContinuous, row by rowBatch loads (daily, hourly)

Running heavy reports directly on the production database slows down the application and gives numbers that change between two refreshes. That is usually the moment a company needs a warehouse.

Data warehouse, data lake, data mart: what is the difference?

TermWhat it storesTypical use
Data warehouseStructured, cleaned, modelled dataReporting, KPIs, dashboards
Data martA subset of the warehouse for one departmentFinance, sales or HR reporting
Data lakeRaw files of any format (CSV, JSON, logs, images)Data science, archiving, ingestion
LakehouseA data lake with warehouse features (tables, SQL, transactions)Both analytics and data science

For a company that mainly needs reliable reporting, a well-modelled warehouse on PostgreSQL or SQL Server is often enough. A data lake becomes useful with large volumes of unstructured data.

What is a star schema?

A star schema has two kinds of tables:

  • Fact table — one row per measured event at a precise grain (for example one receipt line). It holds numeric measures (quantity, amount) and foreign keys to the dimensions.
  • Dimension tables — the descriptive context used to filter and group: date, product, store, customer.

Here is the star schema of a retail chain with several stores:

                    ┌──────────────┐
                    │   dim_date   │
                    │  date_key PK │
                    └──────┬───────┘
                           │
┌──────────────┐   ┌───────┴────────┐   ┌───────────────┐
│  dim_store   │   │   fact_sales   │   │  dim_product  │
│ store_key PK ├───┤ date_key FK    ├───┤ product_key PK│
└──────────────┘   │ store_key FK   │   └───────────────┘
                   │ product_key FK │
                   │ customer_key FK│
                   │ quantity       │
                   │ sales_amount   │
                   └───────┬────────┘
                           │
                   ┌───────┴────────┐
                   │  dim_customer  │
                   │customer_key PK │
                   └────────────────┘

The grain of this example: one row of fact_sales = one product line on one receipt. Defining the grain first is the most important modelling decision.

How do you create a star schema in SQL?

The statements below are written for PostgreSQL and can be run as-is in a new database.

Step 1 — Create the dimensions

CREATE TABLE dim_date (
  date_key      INT PRIMARY KEY,          -- e.g. 20261012
  full_date     DATE NOT NULL UNIQUE,
  day_of_month  SMALLINT NOT NULL,
  month_number  SMALLINT NOT NULL,
  month_name    VARCHAR(20) NOT NULL,
  quarter       SMALLINT NOT NULL,
  year_number   SMALLINT NOT NULL,
  is_weekend    BOOLEAN NOT NULL
);

CREATE TABLE dim_store (
  store_key     INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  store_code    VARCHAR(20) NOT NULL UNIQUE,  -- code in the source system
  store_name    VARCHAR(100) NOT NULL,
  city          VARCHAR(60) NOT NULL,
  region        VARCHAR(60) NOT NULL,
  store_type    VARCHAR(30)                   -- hypermarket, proximity...
);

CREATE TABLE dim_product (
  product_key   INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  product_code  VARCHAR(30) NOT NULL UNIQUE,
  product_name  VARCHAR(150) NOT NULL,
  category      VARCHAR(60) NOT NULL,
  subcategory   VARCHAR(60),
  brand         VARCHAR(60)
);

CREATE TABLE dim_customer (
  customer_key  INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  customer_code VARCHAR(30) NOT NULL,
  segment       VARCHAR(30),                  -- loyalty, occasional...
  city          VARCHAR(60),
  valid_from    DATE NOT NULL DEFAULT CURRENT_DATE,
  valid_to      DATE,                          -- NULL = current version
  is_current    BOOLEAN NOT NULL DEFAULT TRUE
);

dim_customer includes valid_from, valid_to and is_current: this is a slowly changing dimension of type 2. When a customer moves from Rabat to Casablanca, a new version of the row is created, so past sales stay attached to the old city.

Step 2 — Create the fact table

CREATE TABLE fact_sales (
  sale_line_id      BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  date_key          INT NOT NULL REFERENCES dim_date (date_key),
  store_key         INT NOT NULL REFERENCES dim_store (store_key),
  product_key       INT NOT NULL REFERENCES dim_product (product_key),
  customer_key      INT REFERENCES dim_customer (customer_key), -- NULL if anonymous
  receipt_number    VARCHAR(30) NOT NULL,
  quantity          INT NOT NULL,
  unit_price_mad    NUMERIC(12, 2) NOT NULL,
  discount_mad      NUMERIC(12, 2) NOT NULL DEFAULT 0,
  sales_amount_mad  NUMERIC(12, 2) NOT NULL
);

CREATE INDEX idx_fact_sales_date     ON fact_sales (date_key);
CREATE INDEX idx_fact_sales_store    ON fact_sales (store_key);
CREATE INDEX idx_fact_sales_product  ON fact_sales (product_key);
CREATE INDEX idx_fact_sales_customer ON fact_sales (customer_key);

Step 3 — Fill the date dimension

INSERT INTO dim_date
SELECT
  TO_CHAR(d, 'YYYYMMDD')::INT,
  d::DATE,
  EXTRACT(DAY FROM d),
  EXTRACT(MONTH FROM d),
  TO_CHAR(d, 'FMMonth'),
  EXTRACT(QUARTER FROM d),
  EXTRACT(YEAR FROM d),
  EXTRACT(ISODOW FROM d) IN (6, 7)
FROM generate_series('2024-01-01'::DATE, '2027-12-31'::DATE, INTERVAL '1 day') AS d;

The other dimensions and the fact table are filled by the ETL/ELT pipeline from the source systems, which translates source codes into surrogate keys (store_key, product_key…).

Step 4 — Query the warehouse

Once loaded, a typical business question becomes a simple query:

-- Revenue by region and product category in 2026
SELECT
  s.region,
  p.category,
  SUM(f.sales_amount_mad) AS revenue_mad,
  SUM(f.quantity)         AS units_sold
FROM fact_sales f
JOIN dim_date    d ON d.date_key    = f.date_key
JOIN dim_store   s ON s.store_key   = f.store_key
JOIN dim_product p ON p.product_key = f.product_key
WHERE d.year_number = 2026
GROUP BY s.region, p.category
ORDER BY revenue_mad DESC;

Every query follows the same pattern: the fact table in the middle, joins to the dimensions you need, filters and groups on dimension columns. This is exactly what Power BI does behind the scenes, which is why the star schema in Power BI works so well on top of a warehouse.

Which tools are used to build a data warehouse?

LayerCommon options
StoragePostgreSQL, SQL Server, Oracle, MySQL; in the cloud BigQuery, Snowflake, Azure Synapse / Microsoft Fabric, Redshift
Loading and transformationPython, SQL, dbt, KNIME, SSIS, Talend
OrchestrationAirflow, Dagster, cron, SQL Server Agent
ReportingPower BI, Tableau, Looker Studio

In Morocco, companies with many transactions and several systems to reconcile — banks, insurers, telecom operators, retail chains, large public bodies — are the typical users of data warehouses. Smaller companies can start with a single PostgreSQL database and a few well-defined fact tables. Microsoft's guide on star schema design is a solid reference for the modelling rules.

A real example of a star-schema warehouse

The decision-support system on CNSS-style data is an academic BI project built on this approach: a star schema (fact table and dimensions) modelled for social-security-type data, ETL flows in KNIME and Python to clean and standardise scattered flat files, a MySQL data warehouse, and Tableau dashboards filterable by period, region and category.

Common modelling mistakes

  • No defined grain — mixing receipt lines and daily totals in the same fact table produces double counting.
  • Using source codes as keys — surrogate keys protect the warehouse when a source system changes its codes.
  • Text measures — amounts stored as text cannot be summed reliably.
  • No date dimension — every time-based analysis then needs custom date functions.
  • Snowflaking everything — splitting dimensions into many sub-tables makes queries and BI models harder; keep dimensions flat when possible.

Conclusion

A data warehouse is not just "a bigger database": it is a model designed to answer business questions with consistent, historised figures. Start with one business process, define the grain, build a fact table and a few dimensions, and grow from there. To go further, the data engineering service and the Data & AI Engineer page describe how such warehouses are designed and fed.

Data warehouseStar schemaDimensional modellingSQLPostgreSQLBusiness Intelligence
Anass Essadikine

About the author

Anass Essadikine

Data & AI Engineer and Software Engineer based in Casablanca, Morocco. I build data platforms, Power BI dashboards, RAG assistants and Next.js / Spring Boot applications.

Related projects

Frequently asked questions

A data warehouse is a type of database, but it is designed for analysis rather than operations. It gathers data from several sources, keeps history and uses a model, usually a star schema, optimised for aggregations.

Keep reading

All articles

Looking for someone with these skills?

I'm open to freelance projects, internships and full-time roles. Let's talk.