Why This Matters
Bad schema:
orders: id, customer_name, customer_email, product_name,
product_price, quantity, total
Problems:
– Customer data duplicated in every order
– Update customer email → must update 1000 order rows
– Product price change → old orders show wrong price
– Inconsistencies inevitable
Good schema:
customers: id, name, email
products: id, name, price
orders: id, customer_id, product_id, quantity, total
Benefits:
– Single source of truth for customers/products
– Updates happen once
– Consistency guaranteed
This is data modeling—organizing data to prevent problems.
What You’ll Learn
- What normalization means (eliminating redundancy)
- The three normal forms (1NF, 2NF, 3NF)
- When to denormalize (performance trade-offs)
- Common modeling patterns (one-to-many, many-to-many)
First Normal Form (1NF): Atomic Values
Violation Example
CREATE TABLE students (
id INT,
name VARCHAR(100),
courses VARCHAR(500) -- "Math, Physics, Chemistry"
);
Problems:
– Can’t query “students taking Physics” efficiently
– Can’t enforce course existence
– Awkward parsing required
Fixed: One Value Per Cell
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE enrollments (
student_id INT,
course VARCHAR(100),
FOREIGN KEY (student_id) REFERENCES students(id)
);
Benefits:
– Query students in specific course: simple WHERE clause
– Each enrollment is independent row
– Can add enrollment details (grade, date)
Rule: No repeating groups, each cell contains single value.
Second Normal Form (2NF): No Partial Dependencies
Violation Example
CREATE TABLE order_items (
order_id INT,
product_id INT,
product_name VARCHAR(100), -- Depends only on product_id!
product_price DECIMAL, -- Depends only on product_id!
quantity INT,
PRIMARY KEY (order_id, product_id)
);
Problem: product_name and product_price depend on product_id alone, not the full key (order_id, product_id).
Consequences:
– Product name duplicated across all orders
– Change product name → update 1000 rows
– Inconsistency risk
Fixed: Separate Product Table
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
Rule: No non-key column depends on only part of the primary key.
Third Normal Form (3NF): No Transitive Dependencies
Violation Example
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
department VARCHAR(100),
department_location VARCHAR(100) -- Depends on department!
);
Problem: department_location depends on department, which depends on id → transitive dependency.
Consequence: Update department location → must update all employees in that department.
Fixed: Separate Department Table
CREATE TABLE departments (
name VARCHAR(100) PRIMARY KEY,
location VARCHAR(100)
);
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(100),
department VARCHAR(100),
FOREIGN KEY (department) REFERENCES departments(name)
);
Rule: No non-key column depends on another non-key column.
Relationship Patterns
One-to-Many
Example: One customer, many orders.
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100)
);
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
order_date DATE,
total DECIMAL,
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
Pattern: Foreign key in “many” side points to “one” side.
Many-to-Many
Example: Students enroll in courses, courses have multiple students.
Wrong (can’t represent with single foreign key):
-- Can't do this
CREATE TABLE students (
id INT PRIMARY KEY,
course_id INT -- Only one course?!
);
Right: Junction table.
CREATE TABLE students (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE courses (
id INT PRIMARY KEY,
name VARCHAR(100)
);
CREATE TABLE enrollments (
student_id INT,
course_id INT,
grade VARCHAR(2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
Pattern: Junction table with two foreign keys.
One-to-One
Example: User has one profile.
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(100),
password_hash VARCHAR(255)
);
CREATE TABLE user_profiles (
user_id INT PRIMARY KEY,
bio TEXT,
avatar_url VARCHAR(255),
FOREIGN KEY (user_id) REFERENCES users(id)
);
When to use: Separate frequently-accessed data from rarely-accessed data.
When to Denormalize
Normalized: Multiple Joins
-- Get order with customer name and product details
SELECT o.id, c.name, p.name, oi.quantity, p.price
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON oi.product_id = p.id;
Cost: 3 joins per query → slow for large tables.
Denormalized: Redundant Data
-- Copy customer/product info into order_items
CREATE TABLE order_items_denormalized (
order_id INT,
product_id INT,
product_name VARCHAR(100), -- Redundant
product_price DECIMAL, -- Redundant
quantity INT
);
-- Query is simple
SELECT * FROM order_items_denormalized WHERE order_id = 123;
Benefit: Fast reads (no joins).
Cost:
– Data duplication
– Update anomalies (product name changes → must update all order items)
– Inconsistency risk
When to Denormalize
Read-heavy workloads:
– Analytics dashboards
– Reporting systems
– Caches
NOT for:
– Transactional systems (OLTP)
– Data with frequent updates
– When consistency is critical
Practical Example: E-Commerce Schema
Normalized Design
CREATE TABLE customers (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(100) UNIQUE
);
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL,
stock_quantity INT
);
CREATE TABLE orders (
id INT PRIMARY KEY,
customer_id INT,
order_date TIMESTAMP,
status VARCHAR(50),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
price_at_purchase DECIMAL, -- Snapshot price
PRIMARY KEY (order_id, product_id),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
Key decision: price_at_purchase in order_items.
Why?: Product price changes, but historical orders show original price.
This is intentional denormalization for business requirement.
Cross-Domain Connections
Code: Data Structures
From CODE-100.2: Data Structures:
Database schema = choosing data structure for persistence.
– Table = list of dictionaries
– Foreign key = pointer/reference
– Index = hash table for fast lookup
Mathematics: Set Theory
From MATH-200.3: Set Theory:
Relationships are set operations:
– JOIN = Cartesian product + filter
– UNION = Set union
– INTERSECT = Set intersection
Pattern Passport
Pattern Observed: HIERARCHICAL STRUCTURE + REPRESENTATION TRADE-OFF
How It Appears Here:
– Normalization = Eliminate redundancy via hierarchy (entities → relationships)
– Denormalization = Trade redundancy for query performance
– Foreign keys = References creating relationships
Representation:
– Tables: Collections of rows (entities)
– Columns: Attributes of entities
– Foreign keys: Relationships between entities
Transformation Rules:
– 1NF: Atomic values
– 2NF: No partial dependencies
– 3NF: No transitive dependencies
– Denormalization: Intentional redundancy for performance
Assumptions:
– Data has identifiable entities and relationships
– Consistency matters (for normalized designs)
– Query patterns known (for denormalization decisions)
Failure Conditions:
1. Update anomalies: Changing one fact requires many row updates
2. Inconsistency: Same fact stored differently in different places
3. Performance: Excessive joins slow queries
4. Complexity: Over-normalized schema hard to query
Related Disciplines:
– Code CODE-100.2: In-memory data organization
– Math MATH-200.3: Set operations
Next Learning Steps:
1. DATA-100.3: SQL Fundamentals — Querying normalized data
2. DATA-200.1: Indexing Strategies — Making queries fast
3. DATA-200.3: Data Pipelines — ETL and transformations
Summary: Organization Prevents Problems
Data modeling = designing schema to ensure consistency and performance.
Three key insights:
- Normalization eliminates redundancy: Single source of truth
- Relationships via foreign keys: Link entities without duplication
- Denormalization is trade-off: Speed vs. consistency
Normal forms:
– 1NF: Atomic values
– 2NF: No partial key dependencies
– 3NF: No transitive dependencies
When to denormalize:
– Read-heavy workloads
– Known query patterns
– Performance critical
– Consistency less critical
The value: Good schema design prevents data quality disasters, makes queries efficient, enables system scalability.
Exercises
-
Normalize: Given
books(id, title, author_name, author_birthdate, publisher_name, publisher_city), design normalized schema. -
Identify violation: What normal form is violated?
employees(id, name, project, project_budget, project_manager) -
Many-to-many: Design schema for “authors write books, books have multiple authors.”
-
Denormalize trade-off: For analytics dashboard showing daily sales by product category, would you normalize or denormalize? Why?
Revision History
2026-07-30: Substantially revised with concrete examples, normal forms explained, denormalization trade-offs.
2024-09-10: Originally published.
Reproducible Code
Available at:
– Code/schema_examples.sql
– validation/test_normalization.py
cd Categories/06-Data-and-Microsoft-Fabric/100.2-Data-Modeling
python validation/test_normalization.py