DWH Interview Question 14 - What is dimensional modeling?

Data Warehouse Interview Q&A · Question 14

In this post we are going to cover dimensional modeling, the way most data warehouses organize data for reporting.

Dimensional modeling is the approach made popular by Ralph Kimball. It splits warehouse data into two kinds of tables. Fact tables hold the numbers from business events, like quantity sold and sales amount. Dimension tables hold the descriptive context around those numbers: who, what, where and when. The fact sits in the middle and the dimensions sit around it, which is why the shape is called a star schema.

We will build a small star for TB Shop with one fact table and four dimensions, and then see how every report question turns into the same simple query pattern.

Question 14 of the Real DWH Question and Answers series on TechBrothersIT.

The interview answer

Say this first

Dimensional modeling (Ralph Kimball's approach) organizes warehouse data into fact tables, which store measurements of business events such as quantity and amount, and dimension tables, which store the descriptive context: who, what, where and when. Arranged as a star schema, it makes analytical queries simple and fast.

Read the business question first

Here is a trick that makes dimensional modeling easy. Write down a real business question, for example:

"Show me sales amount by product category, by month, by store."

  • The number you want to add up, sales amount, is a fact. Facts are numeric and come from business events, like a sale.
  • Every word after "by" (product category, month, store) points to a dimension. Dimensions are how you slice, filter and group the facts.

Do this for ten questions from the business and you have most of your model before you write any SQL.

Let's create some sample data

We build four dimensions (dim_date, dim_product, dim_customer, dim_store) and one fact table, fact_sales, with one row per product sold on an order line. The fact holds one key per dimension, plus two measures: quantity and amount.

Sample data — run this first
DROP TABLE IF EXISTS fact_sales;
DROP TABLE IF EXISTS dim_date;
DROP TABLE IF EXISTS dim_product;
DROP TABLE IF EXISTS dim_customer;
DROP TABLE IF EXISTS dim_store;

-- dimensions: the descriptive context (who, what, where, when)
CREATE TABLE dim_date (
  date_key   INT PRIMARY KEY,   -- e.g. 20260105
  full_date  DATE,
  month_num  INT,
  month_name VARCHAR(10),
  year       INT
);
CREATE TABLE dim_product (
  product_key  INT PRIMARY KEY,
  product_name VARCHAR(50),
  category     VARCHAR(50)
);
CREATE TABLE dim_customer (
  customer_key INT PRIMARY KEY,
  name         VARCHAR(50),
  city         VARCHAR(50)
);
CREATE TABLE dim_store (
  store_key  INT PRIMARY KEY,
  store_name VARCHAR(50),
  channel    VARCHAR(20)
);

-- fact: one row per product sold on an order line (the measurements)
CREATE TABLE fact_sales (
  date_key     INT REFERENCES dim_date(date_key),
  product_key  INT REFERENCES dim_product(product_key),
  customer_key INT REFERENCES dim_customer(customer_key),
  store_key    INT REFERENCES dim_store(store_key),
  quantity     INT,
  amount       DECIMAL(10,2)
);

INSERT INTO dim_date VALUES
  (20260105, '2026-01-05', 1, 'Jan', 2026), (20260109, '2026-01-09', 1, 'Jan', 2026),
  (20260202, '2026-02-02', 2, 'Feb', 2026), (20260214, '2026-02-14', 2, 'Feb', 2026),
  (20260303, '2026-03-03', 3, 'Mar', 2026);
INSERT INTO dim_product VALUES
  (1, 'Laptop Stand', 'Accessories'), (2, 'USB-C Cable', 'Accessories'),
  (3, 'Wireless Mouse', 'Electronics'), (4, 'Desk Lamp', 'Home');
INSERT INTO dim_customer VALUES
  (1, 'Sara Khan', 'Toronto'), (2, 'Omar Ali', 'Dallas'), (3, 'Lina Park', 'Seattle'), (4, 'Mei Chen', 'Vancouver');
INSERT INTO dim_store VALUES (1, 'TB Shop Online', 'Online'), (2, 'TB Shop Toronto', 'Store');
INSERT INTO fact_sales VALUES
  (20260105, 1, 1, 1, 1, 35.00), (20260105, 2, 1, 1, 2, 18.00),
  (20260109, 3, 2, 2, 1, 25.00), (20260202, 4, 1, 1, 1, 30.00),
  (20260214, 1, 3, 1, 2, 70.00), (20260214, 3, 3, 1, 1, 25.00),
  (20260303, 2, 4, 2, 3, 27.00);

Let's look at the fact table first.

Query
SELECT * FROM fact_sales ORDER BY 1;
Result7 rows · 6 columns
date_keyproduct_keycustomer_keystore_keyquantityamount
20260105111135.00
20260105211218.00
20260109322125.00
20260202411130.00
20260214131270.00
20260214331125.00
20260303242327.00

Seven rows. Each row has four dimension keys and two measures. The keys are the lines of the star.

Query
SELECT * FROM dim_product ORDER BY 1;
Result4 rows · 3 columns
product_keyproduct_namecategory
1Laptop StandAccessories
2USB-C CableAccessories
3Wireless MouseElectronics
4Desk LampHome

Each dimension is a table of descriptive attributes. Here category sits right on the product row, so no extra join is needed to get it.

Query
SELECT * FROM dim_store ORDER BY 1;
Result2 rows · 3 columns
store_keystore_namechannel
1TB Shop OnlineOnline
2TB Shop TorontoStore

Two stores, one Online and one Store channel.

Step 1: Sales amount by month by category

Now let's answer the question from the start: sales amount by month and category. The amount comes from the fact table. We join to dim_date for the month and to dim_product for the category, then group.

Query
SELECT d.month_name, p.category,
       SUM(f.amount) AS sales
FROM fact_sales f
JOIN dim_date d    ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
GROUP BY d.month_num, d.month_name, p.category
ORDER BY d.month_num, p.category;
Result6 rows · 3 columns
month_namecategorysales
JanAccessories53.00
JanElectronics25.00
FebAccessories70.00
FebElectronics25.00
FebHome30.00
MarAccessories27.00

January has 53.00 in Accessories and 25.00 in Electronics. February has three categories, and March only has 27.00 in Accessories. Every query against a star looks like this: one join per dimension you need, then GROUP BY.

Step 2: Swap the question - sales by channel

What if the business asks for sales by channel instead? We do not need a new design. We just join a different dimension and group by its attribute.

Query
SELECT s.channel, SUM(f.amount) AS sales
FROM fact_sales f
JOIN dim_store s ON s.store_key = f.store_key
GROUP BY s.channel;
Result2 rows · 2 columns
channelsales
Online178.00
Store52.00

Online brought in 178.00 and Store 52.00. Same pattern, different dimension. That predictability is the real power of a star schema.

Kimball's four steps

Kimball describes four steps to design any dimensional model. Interviewers like it when you list them in order:

  • Choose the business process - order taking, shipments, inventory and so on.
  • Declare the grain - exactly what one fact row means. For us: one product on one order line. This is the most important step.
  • Identify the dimensions - date, product, customer and store.
  • Identify the facts - quantity and amount.

Dimensional vs normalized

A normalized model is designed for writing data safely. A dimensional model is designed for reading and analysis.

Normalized (3NF)Dimensional (star)
DESIGNED FORWriting data safelyReading and analysis
SHAPEMany small tables, many joinsFact + a few wide dimensions
REDUNDANCYNoneDeliberate, in dimensions
USERSApplicationsAnalysts, BI tools

Dimensions are denormalized on purpose: category sits on the product row and month sits on the date row. A common follow-up is Kimball vs Inmon. Inmon builds a normalized enterprise warehouse first, while Kimball builds dimensional models from the start. That comparison has its own post later in the series (question 41).

Follow-up questions interviewers ask

What are Kimball's four steps?

Choose the business process, declare the grain, identify the dimensions, identify the facts.

Kimball vs Inmon?

Inmon: normalized enterprise warehouse first, marts later. Kimball: dimensional models (star schemas) from the start, linked by conformed dimensions.

Is a dimensional model normalized?

No, dimensions are deliberately denormalized; the fact table is narrow and keyed.

The full script

Here is everything from this post in one script. Copy it and run it top to bottom.

Full script — copy and run top to bottom
-- ========================================================================
-- Data Warehouse Interview Q&A #14 - What is dimensional modeling?
-- TechBrothersIT. Sample data is invented for teaching.
-- Standard SQL, tested on PostgreSQL 16. Runs top to bottom in one go.
-- Run it in an empty database or schema: other videos reuse table names
-- like dim_customer, and their leftover tables can block the DROPs below.
-- PostgreSQL example:  CREATE SCHEMA dwh_q14;  SET search_path TO dwh_q14;
-- ========================================================================

-- Step 0: create and load the sample data
DROP TABLE IF EXISTS fact_sales;
DROP TABLE IF EXISTS dim_date;
DROP TABLE IF EXISTS dim_product;
DROP TABLE IF EXISTS dim_customer;
DROP TABLE IF EXISTS dim_store;

-- dimensions: the descriptive context (who, what, where, when)
CREATE TABLE dim_date (
  date_key   INT PRIMARY KEY,   -- e.g. 20260105
  full_date  DATE,
  month_num  INT,
  month_name VARCHAR(10),
  year       INT
);
CREATE TABLE dim_product (
  product_key  INT PRIMARY KEY,
  product_name VARCHAR(50),
  category     VARCHAR(50)
);
CREATE TABLE dim_customer (
  customer_key INT PRIMARY KEY,
  name         VARCHAR(50),
  city         VARCHAR(50)
);
CREATE TABLE dim_store (
  store_key  INT PRIMARY KEY,
  store_name VARCHAR(50),
  channel    VARCHAR(20)
);

-- fact: one row per product sold on an order line (the measurements)
CREATE TABLE fact_sales (
  date_key     INT REFERENCES dim_date(date_key),
  product_key  INT REFERENCES dim_product(product_key),
  customer_key INT REFERENCES dim_customer(customer_key),
  store_key    INT REFERENCES dim_store(store_key),
  quantity     INT,
  amount       DECIMAL(10,2)
);

INSERT INTO dim_date VALUES
  (20260105, '2026-01-05', 1, 'Jan', 2026), (20260109, '2026-01-09', 1, 'Jan', 2026),
  (20260202, '2026-02-02', 2, 'Feb', 2026), (20260214, '2026-02-14', 2, 'Feb', 2026),
  (20260303, '2026-03-03', 3, 'Mar', 2026);
INSERT INTO dim_product VALUES
  (1, 'Laptop Stand', 'Accessories'), (2, 'USB-C Cable', 'Accessories'),
  (3, 'Wireless Mouse', 'Electronics'), (4, 'Desk Lamp', 'Home');
INSERT INTO dim_customer VALUES
  (1, 'Sara Khan', 'Toronto'), (2, 'Omar Ali', 'Dallas'), (3, 'Lina Park', 'Seattle'), (4, 'Mei Chen', 'Vancouver');
INSERT INTO dim_store VALUES (1, 'TB Shop Online', 'Online'), (2, 'TB Shop Toronto', 'Store');
INSERT INTO fact_sales VALUES
  (20260105, 1, 1, 1, 1, 35.00), (20260105, 2, 1, 1, 2, 18.00),
  (20260109, 3, 2, 2, 1, 25.00), (20260202, 4, 1, 1, 1, 30.00),
  (20260214, 1, 3, 1, 2, 70.00), (20260214, 3, 3, 1, 1, 25.00),
  (20260303, 2, 4, 2, 3, 27.00);


-- Step 1: Sales amount by month by category
SELECT d.month_name, p.category,
       SUM(f.amount) AS sales
FROM fact_sales f
JOIN dim_date d    ON d.date_key = f.date_key
JOIN dim_product p ON p.product_key = f.product_key
GROUP BY d.month_num, d.month_name, p.category
ORDER BY d.month_num, p.category;

-- Step 2: Swap the question: sales by channel, same pattern
SELECT s.channel, SUM(f.amount) AS sales
FROM fact_sales f
JOIN dim_store s ON s.store_key = f.store_key
GROUP BY s.channel;

Key points

  • Facts: numeric measurements of business events
  • Dimensions: descriptive context: who, what, where, when
  • Trick: the number is the fact, every word after "by" is a dimension
  • Kimball's 4 steps: process, grain, dimensions, facts
  • Star schema: one join per dimension, simple and fast
Next in this series

Question 15 — Fact tables and additive measures What is a fact table? What are additive, semi-additive and non-additive measures?

Every query and every result on this page was run on PostgreSQL 16 using the script above. The SQL is standard and runs on most databases with small changes (noted in the script). All tables and data are invented demo data for teaching.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.