Chapter 03 · Dimensional Modeling Foundations: Facts, Dimensions, Star Schemas, and Query Semantics

Why Star Schemas Optimize Understandability and Analytical Access Patterns

Build AtlasMart’s first star schema at paid order-line grain and prove that dimensional joins preserve the established sales controls.

Intermediate → Advanced105–125 minutesStar-schema construction labPython 3.13.5 · SQLite 3.46.1 testedLast reviewed: September 2026

Learning outcomes

Chapter 02 ended with an explicit requirement contract: AtlasMart's certified sales population is the set of paid order lines, with 7 line events, 4 distinct paid orders, 9 units, and paid GMV 625. Chapter 03 now turns that semantic contract into a dimensional model. The model is not allowed to change the event population just because a table layout is convenient.

01

Explain why a star schema organizes one measurement process around a fact table and descriptive dimensions.

02

Declare the AtlasMart sales grain as one row per paid order line before selecting keys or measures.

03

Map order date, customer, product, and selling location into dimensions while retaining order_id as transaction identity.

04

Build and query a deterministic star schema without confusing logical semantics with engine-specific physical tuning.

05

Prove that dimensional joins preserve the Chapter 02 control totals.

Executed baseline for this chapter

The local verification scripts in this generated chapter were executed with Python 3.13.5 and SQLite 3.46.1. They use only stable standard-library/SQL features. Learners should still record the versions printed on their machine; the exercises prove semantic contracts on the synthetic fixture, not performance of a production warehouse.

1. The star is a semantic shape, not a drawing style

A star schema places a measurement table—the fact table—at the center and connects it directly to dimensions that describe the measurement's business context. For AtlasMart, the measurement event is a paid order line. The fact therefore stores keys identifying the date, customer, product, and selling location plus measurements such as quantity and extended amount.

The surrounding dimensions answer the who/what/where/when questions with human-readable attributes. They are not merely lookup tables added for normalization. Their job is to make analytical grouping/filtering stable and understandable while keeping the fact row true to one declared grain.

2. Freeze the grain before choosing the columns

The binding sentence for this chapter is:

AtlasMart sales grain

One row in fact_sales represents one paid AtlasMart order line identified by (order_id, line_no) at the business event date. Pending orders are outside this certified sales population.

This sentence determines what can appear safely on the row. Quantity and line extended amount belong to the line. Customer/product/store/date keys describe that line's context. The parent order_id remains useful as a degenerate transaction identifier for distinct-order counts and drill-through, but an order-level total must not be copied onto every line.

Design question Answer for Chapter 03 Why it matters
Business process Paid sales / order capture Keeps pending O1004 out of the certified sales population
Atomic grain One paid order line Prevents header and line measures from sharing one row ambiguously
Dimensions Date, customer, product, store/selling location Defines the descriptive context of each line
Degenerate identity order_id + line_no Preserves source transaction lineage without inventing an order dimension
Measures quantity, extended_amount; unit_price as a non-additive rate Defines what may be aggregated and how

3. Chapter 03 adds one governed reference-data contract

Chapters 01–02 exposed orders.channel values web, mobile, and sales, but they did not define a store master. Prompt 03 requires a store dimension. Rather than silently pretending a store source existed, this chapter introduces a tiny synthetic reference contract: web → S-WEB, mobile → S-MOBILE, and sales → S-SALES. These members represent selling locations/channels, not physical buildings.

This is an additive model contract. It does not change the seven paid line events or their values. A future real store source could replace the synthetic mapping through a controlled migration while preserving the warehouse surrogate keys exposed to facts.

4. Create the first AtlasMart star

Save the following as atlasmart_star.sql. The dimension surrogate keys are warehouse-controlled integers. The date dimension uses a stable calendar key. Key 0 is this lab's explicit Unknown member policy; the numeric choice is local convention, not a universal rule.

atlasmart_star.sql · canonical Chapter 03 fixture
PRAGMA foreign_keys = ON;DROP TABLE IF EXISTS fact_sales;DROP TABLE IF EXISTS dim_store;DROP TABLE IF EXISTS dim_product;DROP TABLE IF EXISTS dim_customer;DROP TABLE IF EXISTS dim_date;CREATE TABLE dim_date (  date_key INTEGER PRIMARY KEY,  full_date TEXT NOT NULL UNIQUE,  calendar_year INTEGER NOT NULL,  calendar_month INTEGER NOT NULL,  day_of_month INTEGER NOT NULL,  day_name TEXT NOT NULL);CREATE TABLE dim_customer (  customer_key INTEGER PRIMARY KEY,  customer_id TEXT NOT NULL UNIQUE,  customer_name TEXT NOT NULL,  segment TEXT NOT NULL,  region TEXT NOT NULL);CREATE TABLE dim_product (  product_key INTEGER PRIMARY KEY,  product_id TEXT NOT NULL UNIQUE,  product_name TEXT NOT NULL,  category TEXT NOT NULL);CREATE TABLE dim_store (  store_key INTEGER PRIMARY KEY,  store_id TEXT NOT NULL UNIQUE,  store_name TEXT NOT NULL,  store_type TEXT NOT NULL);CREATE TABLE fact_sales (  sales_key INTEGER PRIMARY KEY,  date_key INTEGER NOT NULL REFERENCES dim_date(date_key),  customer_key INTEGER NOT NULL REFERENCES dim_customer(customer_key),  product_key INTEGER NOT NULL REFERENCES dim_product(product_key),  store_key INTEGER NOT NULL REFERENCES dim_store(store_key),  order_id TEXT NOT NULL,  line_no INTEGER NOT NULL,  quantity INTEGER NOT NULL CHECK (quantity > 0),  unit_price NUMERIC NOT NULL CHECK (unit_price >= 0),  extended_amount NUMERIC NOT NULL CHECK (extended_amount >= 0),  source_batch_id TEXT NOT NULL,  UNIQUE(order_id, line_no));INSERT INTO dim_date VALUES(20260918,'2026-09-18',2026,9,18,'Friday'),(20260919,'2026-09-19',2026,9,19,'Saturday'),(20260920,'2026-09-20',2026,9,20,'Sunday');INSERT INTO dim_customer VALUES(0,'__UNKNOWN__','Unknown customer','Unknown','Unknown'),(101,'C001','Ada Retail','SMB','North'),(102,'C002','Ben Home','Consumer','West'),(103,'C003','Cyra Labs','Enterprise','East'),(104,'C004','Dara Studio','Consumer','South');INSERT INTO dim_product VALUES(0,'__UNKNOWN__','Unknown product','Unknown'),(201,'P100','Keyboard','Accessories'),(202,'P200','Mouse','Accessories'),(203,'P300','Monitor','Displays'),(204,'P400','Dock','Accessories');INSERT INTO dim_store VALUES(0,'__UNKNOWN__','Unknown selling location','Unknown'),(301,'S-WEB','Web Store','Digital'),(302,'S-MOBILE','Mobile Store','Digital'),(303,'S-SALES','Assisted Sales','Assisted');INSERT INTO fact_sales VALUES(1,20260918,101,201,301,'O1001',1,2,50,100,'BATCH-CH03-001'),(2,20260918,101,202,301,'O1001',2,1,25,25,'BATCH-CH03-001'),(3,20260918,102,203,302,'O1002',1,1,200,200,'BATCH-CH03-001'),(4,20260919,101,204,301,'O1003',1,1,100,100,'BATCH-CH03-001'),(5,20260919,101,202,301,'O1003',2,2,25,50,'BATCH-CH03-001'),(6,20260920,104,201,302,'O1005',1,1,50,50,'BATCH-CH03-001'),(7,20260920,104,204,302,'O1005',2,1,100,100,'BATCH-CH03-001');

5. Query the star by business language

A star query starts with the fact population and joins dimensions for descriptive context. It does not need to reconstruct the operational order/customer/product schema just to answer a category/segment question.

SQL · paid GMV by day and customer segment
SELECT d.full_date,       c.segment,       SUM(f.extended_amount) AS paid_gmv,       COUNT(DISTINCT f.order_id) AS paid_ordersFROM fact_sales AS fJOIN dim_date AS d ON d.date_key = f.date_keyJOIN dim_customer AS c ON c.customer_key = f.customer_keyGROUP BY d.full_date, c.segmentORDER BY d.full_date, c.segment;
Date Segment Paid GMV Distinct paid orders
2026-09-18 Consumer 200 1
2026-09-18 SMB 125 1
2026-09-19 SMB 150 1
2026-09-20 Consumer 150 1

The output is only meaningful because every fact row has the same grain. A dimension join adds context; it does not redefine what a fact row means.

6. Hands-on lab — prove the star preserves the control totals

Place atlasmart_star.sql and the following script in an empty disposable directory, then run python verify_star.py.

verify_star.py
import sqlite3, sysfrom pathlib import Pathconn = sqlite3.connect(":memory:")conn.execute("PRAGMA foreign_keys = ON")conn.executescript(Path("atlasmart_star.sql").read_text(encoding="utf-8"))row = conn.execute("""SELECT COUNT(*) AS fact_rows,       COUNT(DISTINCT order_id) AS paid_orders,       SUM(quantity) AS units,       SUM(extended_amount) AS gmvFROM fact_sales""").fetchone()print("Python", sys.version.split()[0], "SQLite", sqlite3.sqlite_version)print("fact_rows, orders, units, gmv =", row)assert row == (7, 4, 9, 625)joined = conn.execute("""SELECT SUM(f.extended_amount)FROM fact_sales fJOIN dim_date d ON d.date_key=f.date_keyJOIN dim_customer c ON c.customer_key=f.customer_keyJOIN dim_product p ON p.product_key=f.product_keyJOIN dim_store s ON s.store_key=f.store_key""").fetchone()[0]assert joined == 625print("joined control total =", joined)

Expected evidence is (7, 4, 9, 625) followed by joined control total 625. These checks prove the fixture's semantic population and join coverage. They do not prove production query latency, concurrency behavior, compression, or cloud cost.

Cleanup: delete only the two lab files.

7. Controlled failure: design from operational tables

A common shortcut is to mirror orders, order_lines, customers, and products into an analytical database and call it a warehouse. That preserves operational normalization but leaves every BI author responsible for remembering status filters, header/line cardinality, and customer/product join semantics. Another shortcut flattens all descriptors into the fact and calls the result a star; this repeats descriptive context on every line and makes independent attribute maintenance harder.

The repair is not “always denormalize.” It is to preserve the atomic measurement grain, place reusable descriptive context in dimensions, keep transaction identity where it belongs, and then measure any physical performance decision separately.

8. Production judgment and bridge

A useful star schema should make common analytical questions easy to express without hiding the event population. It should also preserve source lineage, tolerate future history techniques, and give the loading process a deterministic way to resolve every dimension foreign key. Those are semantic properties; partitioning, indexes, clustering, and columnar layout come later and are engine-specific.

The next lesson examines fact and dimension roles more closely—especially keys, measures, descriptive width, and different growth patterns.

Knowledge check

Check your understanding

  1. What exactly does one AtlasMart fact_sales row represent?
  2. Why is order_id retained even though there is no dim_order in this chapter?
  3. Why is the Chapter 03 store mapping described as a new contract?
  4. What does the 625 joined total prove?
  5. Does a star schema by itself guarantee faster queries on every engine?
Review the answers

1. One paid order line identified by order_id and line_no at its order date.

2. It is a useful transaction identifier for grouping, distinct-order counts, and drill-through even though it has no separate descriptive dimension here.

3. Because earlier chapters only supplied channel values, not a store master; the mapping is explicitly added rather than retroactively invented.

4. It proves the canonical star joins do not lose or multiply the paid-sales amount in this fixture.

5. No. It improves semantic usability; physical performance depends on engine, data size, storage layout, statistics, workload, cache, and other factors that must be measured.

Summary and next step

The first dimensional model is now grounded in the same source evidence and grain contract as Chapter 02. The star organizes paid order-line measurements around reusable descriptive dimensions without changing the certified sales population.

Next: distinguish fact and dimension characteristics so table roles are chosen from semantics rather than data type or width.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.