In this post we are going to cover the difference between a star schema and a snowflake schema.
Both are ways to arrange a fact table and its dimensions. In a star schema, every dimension is one wide, denormalized table joined directly to the fact table. In a snowflake schema, the dimensions are normalized into sub-tables, for example product, then subcategory, then category, so the model branches out like the arms of a snowflake.
We will build the same product dimension both ways, ask both models the same question, and compare the joins it takes to get the answer.
Question 18 of the Real DWH Question and Answers series on TechBrothersIT.
The interview answer
A star schema has one fact table joined directly to denormalized dimension tables: one join per dimension. A snowflake schema normalizes the dimensions into sub-tables (product → subcategory → category), so queries need more joins but store less duplicate data. Most warehouses prefer the star for simplicity and query speed.
Let's create some sample data
We build the product dimension twice. For the star, dim_product holds name, subcategory and category on one row. For the snowflake, the same data is split into sf_product, sf_subcategory and sf_category. The fact_sales table is the same for both designs.
DROP TABLE IF EXISTS fact_sales;
DROP TABLE IF EXISTS dim_product;
DROP TABLE IF EXISTS sf_product;
DROP TABLE IF EXISTS sf_subcategory;
DROP TABLE IF EXISTS sf_category;
-- STAR: one denormalized product dimension
CREATE TABLE dim_product (
product_key INT PRIMARY KEY,
product_name VARCHAR(50),
subcategory VARCHAR(50),
category VARCHAR(50)
);
INSERT INTO dim_product VALUES
(1, 'Laptop Stand', 'Stands', 'Accessories'),
(2, 'USB-C Cable', 'Cables', 'Accessories'),
(3, 'Wireless Mouse', 'Mice', 'Electronics'),
(4, 'Desk Lamp', 'Lighting', 'Home');
-- SNOWFLAKE: the same dimension normalized into three tables
CREATE TABLE sf_category (category_key INT PRIMARY KEY, category VARCHAR(50));
CREATE TABLE sf_subcategory (subcategory_key INT PRIMARY KEY, subcategory VARCHAR(50),
category_key INT REFERENCES sf_category(category_key));
CREATE TABLE sf_product (product_key INT PRIMARY KEY, product_name VARCHAR(50),
subcategory_key INT REFERENCES sf_subcategory(subcategory_key));
INSERT INTO sf_category VALUES (1, 'Accessories'), (2, 'Electronics'), (3, 'Home');
INSERT INTO sf_subcategory VALUES (10, 'Stands', 1), (20, 'Cables', 1), (30, 'Mice', 2), (40, 'Lighting', 3);
INSERT INTO sf_product VALUES (1, 'Laptop Stand', 10), (2, 'USB-C Cable', 20), (3, 'Wireless Mouse', 30), (4, 'Desk Lamp', 40);
-- the fact table is the same for both designs
CREATE TABLE fact_sales (sale_id INT PRIMARY KEY, product_key INT, amount DECIMAL(10,2));
INSERT INTO fact_sales VALUES (1, 1, 35.00), (2, 2, 18.00), (3, 3, 25.00), (4, 4, 30.00), (5, 1, 70.00), (6, 2, 27.00);Let's look at the star version of the dimension first.
SELECT * FROM dim_product ORDER BY 1;| product_key | product_name | subcategory | category |
|---|---|---|---|
| 1 | Laptop Stand | Stands | Accessories |
| 2 | USB-C Cable | Cables | Accessories |
| 3 | Wireless Mouse | Mice | Electronics |
| 4 | Desk Lamp | Lighting | Home |
One table, four products. Category and subcategory sit right on the product row, and "Accessories" repeats on two rows.
SELECT * FROM sf_product ORDER BY 1;| product_key | product_name | subcategory_key |
|---|---|---|
| 1 | Laptop Stand | 10 |
| 2 | USB-C Cable | 20 |
| 3 | Wireless Mouse | 30 |
| 4 | Desk Lamp | 40 |
In the snowflake, the product row only stores a subcategory_key.
SELECT * FROM sf_subcategory ORDER BY 1;| subcategory_key | subcategory | category_key |
|---|---|---|
| 10 | Stands | 1 |
| 20 | Cables | 1 |
| 30 | Mice | 2 |
| 40 | Lighting | 3 |
Each subcategory points to its category through category_key.
SELECT * FROM sf_category ORDER BY 1;| category_key | category |
|---|---|
| 1 | Accessories |
| 2 | Electronics |
| 3 | Home |
Each category name is now stored once. Less repetition, but the category is two hops away from the product.
SELECT * FROM fact_sales ORDER BY 1;| sale_id | product_key | amount |
|---|---|---|
| 1 | 1 | 35.00 |
| 2 | 2 | 18.00 |
| 3 | 3 | 25.00 |
| 4 | 4 | 30.00 |
| 5 | 1 | 70.00 |
| 6 | 2 | 27.00 |
Six sales, each pointing to a product_key. Both designs use this same fact table.
Step 1: Star schema - sales by category
Let's ask for sales by category in the star. Category is on the product dimension, so we need one join.
SELECT p.category, SUM(f.amount) AS sales
FROM fact_sales f
JOIN dim_product p ON p.product_key = f.product_key
GROUP BY p.category
ORDER BY sales DESC;| category | sales |
|---|---|
| Accessories | 150.00 |
| Home | 30.00 |
| Electronics | 25.00 |
Accessories sold 150.00, Home 30.00 and Electronics 25.00. One join, group by, done. That is simple for analysts and simple for BI tools.
Step 2: Snowflake schema - the same question
Now the same question in the snowflake. We go from the fact to product, then to subcategory, then to category.
SELECT c.category, SUM(f.amount) AS sales
FROM fact_sales f
JOIN sf_product p ON p.product_key = f.product_key
JOIN sf_subcategory s ON s.subcategory_key = p.subcategory_key
JOIN sf_category c ON c.category_key = s.category_key
GROUP BY c.category
ORDER BY sales DESC;| category | sales |
|---|---|
| Accessories | 150.00 |
| Home | 30.00 |
| Electronics | 25.00 |
Exactly the same answer: 150.00, 30.00 and 25.00. The difference is the work: three joins instead of one, for every query, every dashboard and every user.
Star vs snowflake
| Star | Snowflake | |
|---|---|---|
| JOINS | One per dimension | One per hierarchy level |
| STORAGE | Some repetition in dimensions | Less repetition (small saving) |
| EASE OF USE | Simple for analysts and BI | More tables to understand |
| LOADING | Fewer tables and keys | More tables and keys to maintain |
The snowflake does repeat less data. But dimensions are small compared to facts, and columnar warehouses compress repeated values well, so the saving is usually tiny.
The galaxy schema
Real warehouses have more than one fact table. For example, sales (one row per order line) and inventory (one row per product per day) are two business processes with two different grains. When they share the same date and product dimensions, you can compare sales and stock for the same product on the same day.
That shape is called a galaxy schema, or fact constellation. It is not a different technique, just several stars sharing their dimensions. Those shared dimensions are called conformed dimensions.
When does a snowflake make sense?
- Large, shared sub-dimensions - for example one geography table reused by customers, stores and suppliers. Kimball calls that an outrigger.
- Hierarchy already normalized upstream - and flattening it brings no real benefit.
The default interview answer: start with a star, and snowflake only with a clear reason.
The snowflake schema has nothing to do with Snowflake the cloud warehouse. Plenty of teams on Snowflake build star schemas.
Follow-up questions interviewers ask
Which is faster?
Usually the star: fewer joins, and modern columnar engines make dimension redundancy cheap.
What is an outrigger?
A secondary dimension referenced by another dimension, such as a shared geography table.
What is a galaxy (fact constellation) schema?
Several fact tables sharing the same conformed dimensions.
The full script
Here is everything from this post in one script. Copy it and run it top to bottom.
-- ========================================================================
-- Data Warehouse Interview Q&A #18 - Star schema vs snowflake schema
-- 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_q18; SET search_path TO dwh_q18;
-- ========================================================================
-- Step 0: create and load the sample data
DROP TABLE IF EXISTS fact_sales;
DROP TABLE IF EXISTS dim_product;
DROP TABLE IF EXISTS sf_product;
DROP TABLE IF EXISTS sf_subcategory;
DROP TABLE IF EXISTS sf_category;
-- STAR: one denormalized product dimension
CREATE TABLE dim_product (
product_key INT PRIMARY KEY,
product_name VARCHAR(50),
subcategory VARCHAR(50),
category VARCHAR(50)
);
INSERT INTO dim_product VALUES
(1, 'Laptop Stand', 'Stands', 'Accessories'),
(2, 'USB-C Cable', 'Cables', 'Accessories'),
(3, 'Wireless Mouse', 'Mice', 'Electronics'),
(4, 'Desk Lamp', 'Lighting', 'Home');
-- SNOWFLAKE: the same dimension normalized into three tables
CREATE TABLE sf_category (category_key INT PRIMARY KEY, category VARCHAR(50));
CREATE TABLE sf_subcategory (subcategory_key INT PRIMARY KEY, subcategory VARCHAR(50),
category_key INT REFERENCES sf_category(category_key));
CREATE TABLE sf_product (product_key INT PRIMARY KEY, product_name VARCHAR(50),
subcategory_key INT REFERENCES sf_subcategory(subcategory_key));
INSERT INTO sf_category VALUES (1, 'Accessories'), (2, 'Electronics'), (3, 'Home');
INSERT INTO sf_subcategory VALUES (10, 'Stands', 1), (20, 'Cables', 1), (30, 'Mice', 2), (40, 'Lighting', 3);
INSERT INTO sf_product VALUES (1, 'Laptop Stand', 10), (2, 'USB-C Cable', 20), (3, 'Wireless Mouse', 30), (4, 'Desk Lamp', 40);
-- the fact table is the same for both designs
CREATE TABLE fact_sales (sale_id INT PRIMARY KEY, product_key INT, amount DECIMAL(10,2));
INSERT INTO fact_sales VALUES (1, 1, 35.00), (2, 2, 18.00), (3, 3, 25.00), (4, 4, 30.00), (5, 1, 70.00), (6, 2, 27.00);
-- Step 1: Star schema: sales by category (1 join)
SELECT p.category, SUM(f.amount) AS sales
FROM fact_sales f
JOIN dim_product p ON p.product_key = f.product_key
GROUP BY p.category
ORDER BY sales DESC;
-- Step 2: Snowflake schema: the same question (3 joins)
SELECT c.category, SUM(f.amount) AS sales
FROM fact_sales f
JOIN sf_product p ON p.product_key = f.product_key
JOIN sf_subcategory s ON s.subcategory_key = p.subcategory_key
JOIN sf_category c ON c.category_key = s.category_key
GROUP BY c.category
ORDER BY sales DESC;
Key points
- Star: dimensions denormalized, one join each
- Snowflake: dimensions normalized into sub-tables, more joins
- Same answers; the star is simpler and usually faster
- Snowflake saving is small: dimensions are tiny next to facts
- Default to a star; galaxy = facts sharing dimensions
Question 19 — What is a surrogate key? What is a surrogate key, and why do warehouses use them?
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.