Chapter 7

CREATE & ALTER Tables

Learn how to design and build your own database tables from scratch. Master the art of schema design!

📋 Creating Tables with CREATE TABLE

So far, you've been querying existing tables. Now it's time to learn how to create your own tables from scratch!

🎯 Real-Life Analogy: CREATE TABLE is Like Designing a Form

Imagine creating a paper form for a survey:

  • Table Name: "Customer Survey" (what the form is called)
  • Columns: Questions on the form (Name, Email, Age, Feedback)
  • Data Types: Type of answer expected (text, number, date)
  • Constraints: Rules ("Name is required", "Email must be unique")

CREATE TABLE is like designing the blank form - you define what information goes where!

CREATE TABLE Syntax:

CREATE TABLE table_name (
    column1 data_type constraints,
    column2 data_type constraints,
    column3 data_type constraints,
    ...
);

Try It: Create a Simple Table

Output will appear here...

🔍 What Just Happened?

  1. CREATE TABLE students: We created a new table called "students"
  2. Defined columns: id, name, age, grade
  3. Set data types: INTEGER for numbers, TEXT for strings
  4. Added constraints: PRIMARY KEY, NOT NULL
  5. Tested it: Inserted data to make sure it works!

Real-World Example: Creating a Products Table

CREATE TABLE products (
    product_id INTEGER PRIMARY KEY,
    product_name TEXT NOT NULL,
    price REAL,
    stock_quantity INTEGER DEFAULT 0,
    category TEXT,
    created_date TEXT
);

This creates a table to store product information for an e-commerce store!

Try It: Create a Movies Table

Let's create a table to track your favorite movies:

Output will appear here...

Try It: Create a Tasks/To-Do List Table

Perfect for a task management app:

Output will appear here...

🔍 How CREATE TABLE Works Behind the Scenes

When you run CREATE TABLE, here's what happens:

  1. Database checks if table name exists: If a table with that name already exists, you'll get an error (unless you use CREATE TABLE IF NOT EXISTS)
  2. Memory allocation: The database reserves space in its catalog to store the table structure (called metadata)
  3. Schema registration: Column names, data types, and constraints are saved to the system catalog
  4. Index creation: If you defined a PRIMARY KEY, an index is automatically created for fast lookups
  5. Ready for data: The table is now ready to receive data via INSERT statements!

Real-Life Analogy: It's like creating a new file folder:

  • Check if folder name is available ✓
  • Create the folder structure ✓
  • Label all the sections inside ✓
  • Set up an index tab for quick access ✓
  • Ready to file documents! ✓

💡 CREATE TABLE IF NOT EXISTS

Use this safer version to avoid errors if the table already exists:

-- Won't error if students table already exists
CREATE TABLE IF NOT EXISTS students (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
);

-- This pattern is useful in scripts that might run multiple times!

Try It: Using CREATE TABLE IF NOT EXISTS

Output will appear here...

🔤 SQL Data Types

Data types tell SQL what kind of information each column will store.

🎯 Real-Life Analogy: Data Types are Like Container Types

Think about storing different things:

  • INTEGER (number box): Can only hold whole numbers like 42, -17, 1000
  • TEXT (string box): Holds any text like "Hello", "abc123"
  • REAL (decimal box): Holds numbers with decimals like 19.99, 3.14
  • DATE (calendar box): Holds dates like "2024-01-15"

You wouldn't store apples in a shoe box - same idea with data types!

Common SQLite Data Types:

INTEGER

Whole numbers (no decimals)

age INTEGER

Examples: 42, -17, 1000

REAL

Numbers with decimals

price REAL

Examples: 19.99, 3.14159, -0.5

TEXT

Any text or string

name TEXT

Examples: "Alice", "hello@email.com"

BLOB

Binary data (images, files)

photo BLOB

Examples: Images, PDFs, binary files

NULL

Represents missing/unknown

email TEXT

Meaning: No value provided

Other Database Systems (MySQL, PostgreSQL):

Different databases have more specific types:

  • VARCHAR(50) - Text with max length of 50 characters
  • INT - Same as INTEGER
  • DECIMAL(10,2) - Numbers with fixed decimal places
  • DATE - Date only (YYYY-MM-DD)
  • DATETIME - Date and time
  • BOOLEAN - TRUE or FALSE

Try It: Create Table with Various Data Types

Output will appear here...

🔍 Choosing the Right Data Type:

  • Ages, IDs, counts: INTEGER
  • Prices, measurements: REAL or DECIMAL
  • Names, descriptions: TEXT or VARCHAR
  • Dates: TEXT (in SQLite) or DATE (in MySQL/PostgreSQL)
  • True/False flags: INTEGER (0/1) or BOOLEAN

📊 Data Type Decision Guide

Ask yourself these questions:

What are you storing? Use this data type Example
Whole numbers (no decimals) INTEGER age, quantity, user_id
Numbers with decimals REAL price, temperature, rating
Any text or words TEXT name, email, description
True/False values INTEGER (0/1) is_active, is_verified
Dates and times TEXT (SQLite) created_at, birthdate
Files, images, binary data BLOB profile_picture, pdf_file

Practice: Choosing the Right Data Types

Try to create a table with appropriate data types for a blog system:

Output will appear here...

⚠️ Common Beginner Mistakes When Creating Tables

Learn from these common pitfalls to avoid frustration!

🎯 Real-Life Analogy: Mistakes are Like Building Code Violations

When building a house, you might make mistakes like:

  • Forgetting the foundation: No PRIMARY KEY (every table needs one!)
  • Wrong materials: Using TEXT for numbers you want to calculate with
  • No building permits: Missing NOT NULL on required fields
  • Duplicate room names: Creating a table that already exists

These mistakes won't crash your database, but they'll cause problems later!

Mistake #1: Forgetting the PRIMARY KEY

❌ Bad Practice:

CREATE TABLE customers (
    name TEXT,
    email TEXT
);
-- No way to uniquely identify each customer!
-- What if two people have the same name?

✅ Good Practice:

CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
);
-- Each customer has a unique ID
-- Email is unique and required

Why it matters: Without a PRIMARY KEY, you can't reliably update or delete specific rows. Two identical rows become impossible to distinguish!

Mistake #2: Using Wrong Data Types

❌ Bad Practice:

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT,
    price TEXT,  -- ❌ Price as TEXT!
    quantity TEXT  -- ❌ Quantity as TEXT!
);

-- Later you try to calculate total:
-- SELECT price * quantity FROM products;  -- ERROR!

✅ Good Practice:

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price REAL NOT NULL CHECK(price >= 0),
    quantity INTEGER NOT NULL CHECK(quantity >= 0)
);

-- Now calculations work!
-- SELECT price * quantity AS total FROM products;  -- ✓ Works!

See the Difference:

Output will appear here...

Mistake #3: Forgetting NOT NULL on Required Fields

❌ Bad Practice:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username TEXT,  -- ❌ Could be NULL!
    email TEXT  -- ❌ Could be NULL!
);

-- Later someone inserts a user with no email:
INSERT INTO users (id, username) VALUES (1, 'john');
-- Now you have a user with NULL email - problematic!

✅ Good Practice:

CREATE TABLE users (
    id INTEGER PRIMARY KEY,
    username TEXT NOT NULL UNIQUE,
    email TEXT NOT NULL UNIQUE
);

-- This prevents incomplete data:
INSERT INTO users (id, username) VALUES (1, 'john');  -- ERROR!
-- Must provide email too!

Mistake #4: Not Using Constraints for Data Validation

❌ Bad Practice:

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT,
    age INTEGER,  -- ❌ No validation!
    salary REAL  -- ❌ Could be negative!
);

-- Someone could insert invalid data:
INSERT INTO employees VALUES (1, 'Bob', -5, -50000);  -- ❌ Nonsense data!

✅ Good Practice:

CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER CHECK(age >= 18 AND age <= 100),
    salary REAL CHECK(salary >= 0)
);

-- Constraints prevent bad data:
INSERT INTO employees VALUES (1, 'Bob', -5, -50000);  -- ERROR! Prevented!

Try It: See Constraints in Action

Output will appear here...

Mistake #5: Table Names That Cause Confusion

❌ Bad Practice:

-- Unclear or misleading names:
CREATE TABLE data (id INTEGER, info TEXT);  -- Too vague!
CREATE TABLE tbl1 (id INTEGER, val TEXT);  -- Meaningless!
CREATE TABLE USER (id INTEGER, name TEXT);  -- Reserved keyword!

✅ Good Practice:

-- Clear, descriptive names:
CREATE TABLE customer_orders (order_id INTEGER, customer_id INTEGER);
CREATE TABLE product_reviews (review_id INTEGER, product_id INTEGER);
CREATE TABLE app_users (user_id INTEGER, username TEXT);  -- 'users' is safer than 'USER'

💡 Table Naming Best Practices:

  • Be descriptive: Use names that clearly indicate what the table stores
  • Use underscores: separate_words_like_this (not CamelCase)
  • Plural vs singular: Be consistent (either "users" or "user", not both)
  • Avoid reserved keywords: Don't use names like USER, SELECT, TABLE, etc.
  • Keep it simple: Shorter is better, but clarity comes first

🔍 Quick Checklist Before Creating a Table:

Ask yourself these questions:

  1. ✓ Does my table have a PRIMARY KEY?
  2. ✓ Are my data types appropriate for the data I'm storing?
  3. ✓ Did I add NOT NULL to required fields?
  4. ✓ Do I have UNIQUE constraints where needed (emails, usernames)?
  5. ✓ Did I add CHECK constraints for data validation?
  6. ✓ Is my table name clear and descriptive?
  7. ✓ Did I avoid using reserved SQL keywords as table/column names?

If you can answer "yes" to all of these, you're on the right track!

⚡ Constraints - Rules for Your Data

Constraints enforce rules to keep your data clean and consistent!

🎯 Real-Life Analogy: Constraints are Like Form Validation

When filling out an online form:

  • NOT NULL: "This field is required" (can't be empty)
  • UNIQUE: "Email already in use" (must be unique)
  • DEFAULT: Country field pre-filled with "USA"
  • CHECK: "Age must be 18 or older" (validation rule)

Constraints prevent bad data from entering your database!

Common Constraints:

PRIMARY KEY

Unique identifier for each row

id INTEGER PRIMARY KEY

Automatically NOT NULL and UNIQUE

NOT NULL

Column must have a value

name TEXT NOT NULL

Prevents empty/missing data

UNIQUE

No duplicate values allowed

email TEXT UNIQUE

Ensures uniqueness (like usernames)

DEFAULT

Sets a default value

status TEXT DEFAULT 'active'

Uses default if nothing provided

CHECK

Custom validation rule

age INTEGER CHECK(age >= 18)

Enforces business logic

FOREIGN KEY

Links to another table

dept_id INTEGER REFERENCES departments(id)

Maintains relationships

Try It: Create Table with Multiple Constraints

Output will appear here...

Try It: See Constraints in Action (This Will Fail!)

Watch how constraints prevent bad data:

Output will appear here...

You should see an error - constraints are working to protect your data!

🔍 Why Use Constraints?

  • Data Integrity: Prevents invalid data from entering the database
  • Consistency: Ensures all data follows the same rules
  • Error Prevention: Catches mistakes before they become problems
  • Documentation: Constraints describe business rules in the database itself

Troubleshooting Common Constraint Errors

Error 1: "NOT NULL constraint failed"

What it means: You tried to insert a row without providing a value for a NOT NULL column

-- This will fail:
INSERT INTO users (id, username) VALUES (1, 'alice');
-- Error: NOT NULL constraint failed: users.email

-- Solution: Provide all required fields
INSERT INTO users (id, username, email) VALUES (1, 'alice', 'alice@email.com');

Error 2: "UNIQUE constraint failed"

What it means: You tried to insert a value that already exists in a UNIQUE column

-- First insert succeeds:
INSERT INTO users (id, username, email) VALUES (1, 'alice', 'alice@email.com');

-- This will fail (duplicate email):
INSERT INTO users (id, username, email) VALUES (2, 'bob', 'alice@email.com');
-- Error: UNIQUE constraint failed: users.email

-- Solution: Use a different, unique value
INSERT INTO users (id, username, email) VALUES (2, 'bob', 'bob@email.com');

Error 3: "CHECK constraint failed"

What it means: The value you're trying to insert doesn't meet the CHECK condition

CREATE TABLE products (
    id INTEGER PRIMARY KEY,
    name TEXT,
    price REAL CHECK(price >= 0)
);

-- This will fail:
INSERT INTO products VALUES (1, 'Laptop', -100);
-- Error: CHECK constraint failed: products

-- Solution: Provide a valid value
INSERT INTO products VALUES (1, 'Laptop', 999.99);

Error 4: "FOREIGN KEY constraint failed"

What it means: You're trying to insert a foreign key value that doesn't exist in the referenced table

-- Try to create an order for customer_id=999 (doesn't exist):
INSERT INTO orders (order_id, customer_id) VALUES (1, 999);
-- Error: FOREIGN KEY constraint failed

-- Solution: First create the customer, OR use an existing customer_id
INSERT INTO customers (customer_id, name) VALUES (999, 'New Customer');
INSERT INTO orders (order_id, customer_id) VALUES (1, 999);  -- Now works!

💡 Constraint Error Quick Fix Guide:

Error Message What You Forgot How to Fix
NOT NULL constraint failed Required field missing Provide value for all NOT NULL columns
UNIQUE constraint failed Value already exists Use a different unique value
CHECK constraint failed Value violates rules Check the CHECK condition and provide valid value
FOREIGN KEY constraint failed Referenced record doesn't exist Create the referenced record first

Practice: Fix the Constraint Errors

Each statement below has an error. Uncomment them one by one and see what happens:

Output will appear here...

🔑 Primary Keys Explained

Every table needs a unique identifier - that's the primary key!

🎯 Real-Life Analogy: Primary Keys are Like Student ID Numbers

In a school:

  • Student Name: "John Smith" - Multiple Johns might exist (not unique!)
  • Student ID: "STU-12345" - Unique to each student (perfect identifier!)

Even if two students have the same name, their ID numbers are always different!

Primary Key Rules:

  • Must be UNIQUE (no duplicates)
  • Cannot be NULL (must have a value)
  • Should never change (stable identifier)
  • Each table can have only ONE primary key
  • Often an INTEGER that auto-increments

Try It: Primary Key with AUTOINCREMENT

Output will appear here...

🔍 How AUTOINCREMENT Works:

  1. First row gets ID = 1
  2. Second row gets ID = 2
  3. Third row gets ID = 3
  4. Even if you delete row 2, the next new row gets ID = 4 (numbers never reused!)

You don't have to manually assign IDs - the database does it for you!

Common Primary Key Patterns:

-- Pattern 1: Simple integer ID
CREATE TABLE products (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    ...
);

-- Pattern 2: Composite primary key (multiple columns)
CREATE TABLE enrollments (
    student_id INTEGER,
    course_id INTEGER,
    PRIMARY KEY (student_id, course_id)
);

-- Pattern 3: UUID/GUID (advanced)
CREATE TABLE sessions (
    session_id TEXT PRIMARY KEY,  -- e.g., "a1b2c3d4-..."
    ...
);

🔧 ALTER TABLE - Modifying Existing Tables

Need to change a table structure after it's created? Use ALTER TABLE!

🎯 Real-Life Analogy: ALTER TABLE is Like Renovating a Building

Your house (table) is already built, but you want to:

  • Add a new room: ALTER TABLE ADD COLUMN
  • Remove a room: ALTER TABLE DROP COLUMN
  • Rename the house: ALTER TABLE RENAME TO
  • Remodel a room: ALTER TABLE MODIFY COLUMN

ALTER lets you modify without tearing down and rebuilding!

1. Adding New Columns:

Try It: Add Column to Existing Table

Output will appear here...

ALTER TABLE ADD COLUMN Syntax:

ALTER TABLE table_name
ADD COLUMN column_name data_type constraints;

Note: Existing rows will have NULL for the new column (unless you specify DEFAULT)

2. Renaming Tables:

Try It: Rename a Table

Output will appear here...

🔍 SQLite ALTER Limitations:

SQLite has limited ALTER capabilities compared to other databases:

  • ✅ Can: ADD COLUMN
  • ✅ Can: RENAME TABLE
  • ✅ Can: RENAME COLUMN (SQLite 3.25+)
  • ❌ Cannot: DROP COLUMN (need workaround)
  • ❌ Cannot: MODIFY COLUMN (need workaround)
  • ❌ Cannot: Add/Remove constraints on existing columns

MySQL and PostgreSQL support more ALTER operations!

Workaround: Recreating Table to Remove Column

-- Steps to remove a column in SQLite:

-- 1. Create new table without the unwanted column
CREATE TABLE employees_new (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL
    -- removed 'age' column
);

-- 2. Copy data from old table
INSERT INTO employees_new SELECT id, name FROM employees_old;

-- 3. Drop old table
DROP TABLE employees_old;

-- 4. Rename new table
ALTER TABLE employees_new RENAME TO employees_old;

🗑️ DROP TABLE - Deleting Tables

Need to completely remove a table? Use DROP TABLE (but be careful!)

⚠️ Warning: DROP TABLE is Permanent!

Think of DROP TABLE like demolishing a building:

  • The table structure is completely deleted
  • All data inside is permanently lost
  • There's NO UNDO button!

Always backup your data before using DROP TABLE!

DROP TABLE Syntax:

DROP TABLE table_name;

-- Safer version (won't error if table doesn't exist):
DROP TABLE IF EXISTS table_name;

Try It: Create and Drop a Test Table

Output will appear here...

Safe Dropping Pattern:

-- Check if table exists before dropping
DROP TABLE IF EXISTS old_data;

-- Create fresh table
CREATE TABLE new_data (
    id INTEGER PRIMARY KEY,
    info TEXT
);

-- This won't error even if old_data didn't exist

🏗️ Schema Design Basics

Learn to design good database structures - the foundation of great applications!

🎯 What is a Schema?

A database schema is the blueprint of your database - the overall structure of tables, columns, relationships, and constraints.

Think of it like architectural plans for a house:

  • Tables: Rooms in the house
  • Columns: Features of each room
  • Relationships: How rooms connect (hallways, doors)
  • Constraints: Building codes and rules

Good Schema Design Principles:

1. Normalization

Avoid data duplication

Store each piece of info once

2. Meaningful Names

Use clear table/column names

users, products, order_items

3. Primary Keys

Every table needs a unique ID

Use INTEGER AUTOINCREMENT

4. Right Data Types

Choose appropriate types

INTEGER for counts, REAL for prices

5. Constraints

Enforce data integrity

NOT NULL, UNIQUE, CHECK

6. Documentation

Add comments explaining tables

Make intent clear

Example: Well-Designed E-Commerce Schema

-- Customers table
CREATE TABLE customers (
    customer_id INTEGER PRIMARY KEY AUTOINCREMENT,
    email TEXT NOT NULL UNIQUE,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

-- Products table
CREATE TABLE products (
    product_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    description TEXT,
    price REAL NOT NULL CHECK(price >= 0),
    stock_quantity INTEGER DEFAULT 0
);

-- Orders table
CREATE TABLE orders (
    order_id INTEGER PRIMARY KEY AUTOINCREMENT,
    customer_id INTEGER NOT NULL,
    order_date TEXT DEFAULT CURRENT_TIMESTAMP,
    total_amount REAL,
    FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
);

-- Order Items (junction table for many-to-many)
CREATE TABLE order_items (
    order_id INTEGER,
    product_id INTEGER,
    quantity INTEGER NOT NULL CHECK(quantity > 0),
    price_at_purchase REAL NOT NULL,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id) REFERENCES orders(order_id),
    FOREIGN KEY (product_id) REFERENCES products(product_id)
);

🔍 Why This Design is Good:

  • No duplication: Customer info stored once, not repeated in every order
  • Clear relationships: Foreign keys link tables properly
  • Data integrity: Constraints prevent invalid data (negative prices, empty names)
  • Scalable: Can add new products/customers without redesigning
  • Junction table: order_items handles many-to-many relationship

Understanding Database Normalization

🎯 What is Normalization?

Normalization is the process of organizing data to reduce redundancy and improve data integrity.

Real-Life Analogy: Imagine organizing your kitchen:

  • Before (not normalized): You keep salt, pepper, and spices scattered everywhere - in every cabinet, drawer, and countertop. Lots of duplication!
  • After (normalized): You have ONE spice rack where all spices are stored. No duplication, easy to find, easy to update!

Same idea with databases - store each piece of information in ONE place!

❌ Bad Design: Not Normalized (Data Duplication)

Problem: Customer information repeated in every order!

CREATE TABLE orders_bad (
    order_id INTEGER PRIMARY KEY,
    customer_name TEXT,
    customer_email TEXT,
    customer_address TEXT,
    product_name TEXT,
    product_price REAL,
    quantity INTEGER
);

-- Sample data showing duplication:
-- Order 1: John Doe, john@email.com, 123 Main St, Laptop, 999.99, 1
-- Order 2: John Doe, john@email.com, 123 Main St, Mouse, 29.99, 2
-- Order 3: John Doe, john@email.com, 123 Main St, Keyboard, 79.99, 1

-- Problems:
-- 1. John's info repeated 3 times (waste of space!)
-- 2. If John changes email, must update 3 rows (error-prone!)
-- 3. Misspelling John's name in one order causes inconsistency

✅ Good Design: Normalized (No Duplication)

Solution: Separate tables with relationships!

-- Customer info stored ONCE
CREATE TABLE customers_normalized (
    customer_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    address TEXT
);

-- Product info stored ONCE
CREATE TABLE products_normalized (
    product_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    price REAL NOT NULL
);

-- Orders only store IDs (references to other tables)
CREATE TABLE orders_normalized (
    order_id INTEGER PRIMARY KEY,
    customer_id INTEGER,
    product_id INTEGER,
    quantity INTEGER,
    FOREIGN KEY (customer_id) REFERENCES customers_normalized(customer_id),
    FOREIGN KEY (product_id) REFERENCES products_normalized(product_id)
);

-- Benefits:
-- 1. Customer info stored ONCE - no duplication!
-- 2. Update John's email in ONE place - affects all orders automatically!
-- 3. Consistent data - can't have typos in repeated data!

Try It: See Normalization Benefits

Output will appear here...

Bad vs Good Design: More Examples

Example 1: Student Enrollment System

❌ Bad Design (Denormalized):

CREATE TABLE enrollments_bad (
    enrollment_id INTEGER PRIMARY KEY,
    student_name TEXT,
    student_email TEXT,
    student_major TEXT,
    course_name TEXT,
    course_instructor TEXT,
    course_credits INTEGER
);

-- If student takes 5 courses, their info is repeated 5 times!
-- If instructor name changes, must update many rows!

✅ Good Design (Normalized):

CREATE TABLE students (
    student_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    major TEXT
);

CREATE TABLE courses (
    course_id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    instructor TEXT NOT NULL,
    credits INTEGER
);

CREATE TABLE enrollments_good (
    enrollment_id INTEGER PRIMARY KEY,
    student_id INTEGER,
    course_id INTEGER,
    grade TEXT,
    FOREIGN KEY (student_id) REFERENCES students(student_id),
    FOREIGN KEY (course_id) REFERENCES courses(course_id)
);

-- Student info stored once, course info stored once!
-- Enrollments just link them together

Example 2: Library System

❌ Bad Design:

CREATE TABLE checkouts_bad (
    checkout_id INTEGER PRIMARY KEY,
    member_name TEXT,
    member_phone TEXT,
    book_title TEXT,
    book_author TEXT,
    book_isbn TEXT,
    checkout_date TEXT,
    due_date TEXT
);

-- Book details repeated every time someone checks it out!
-- Member info repeated for every book they borrow!

✅ Good Design:

CREATE TABLE members (
    member_id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    phone TEXT,
    email TEXT UNIQUE
);

CREATE TABLE books (
    book_id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    author TEXT NOT NULL,
    isbn TEXT UNIQUE
);

CREATE TABLE checkouts_good (
    checkout_id INTEGER PRIMARY KEY AUTOINCREMENT,
    member_id INTEGER NOT NULL,
    book_id INTEGER NOT NULL,
    checkout_date TEXT DEFAULT CURRENT_TIMESTAMP,
    due_date TEXT NOT NULL,
    return_date TEXT,
    FOREIGN KEY (member_id) REFERENCES members(member_id),
    FOREIGN KEY (book_id) REFERENCES books(book_id)
);

-- Clean, organized, no duplication!

🎓 The 3 Normal Forms (Simplified)

First Normal Form (1NF): No repeating groups - each cell has only ONE value

  • ❌ Bad: phone_numbers = "555-1234, 555-5678, 555-9012" (multiple values in one cell)
  • ✅ Good: Create separate rows or a phone_numbers table

Second Normal Form (2NF): No partial dependencies - all columns depend on the entire primary key

  • ❌ Bad: In order_items(order_id, product_id, customer_name) - customer_name only depends on order_id, not product_id
  • ✅ Good: Move customer_name to orders table

Third Normal Form (3NF): No transitive dependencies - no column depends on another non-key column

  • ❌ Bad: employees(id, department_id, department_name) - department_name depends on department_id, not id
  • ✅ Good: Create separate departments table

Simple Rule: Each table should be about ONE thing, and store that thing's info ONCE!

Practice: Design a Normalized Schema

Try creating a normalized schema for a movie rental system:

Output will appear here...

🔍 Schema Design Checklist:

Before finalizing your database schema, ask yourself:

  1. Is each table about ONE thing? (customers, products, orders - not all mixed together)
  2. Is data stored only ONCE? (no repeating customer names in every order)
  3. Do all tables have PRIMARY KEYS?
  4. Are relationships properly defined with FOREIGN KEYS?
  5. Are data types appropriate? (INTEGER for IDs, REAL for prices)
  6. Are constraints in place? (NOT NULL, UNIQUE, CHECK)
  7. Are table and column names clear and consistent?
  8. Can the schema handle future growth? (adding new products/users without redesign)

Practice Exercises

Part 1: Guided Exercises

Guided Exercise 1: Create a Library Table

Task: Create a table called "library_books" with columns for: id (primary key), title (required), author (required), isbn (unique), year_published (integer), available (default 1 for true).

Output will appear here...

Part 2: Independent Practice

Guided Exercise 2: Create a Products Table

Task: Create a table for an online store with: product_id (PK, autoinc), name (required), category (required), price (real, must be > 0), stock (integer, default 0), is_available (default 1).

Output will appear here...

Guided Exercise 3: Add a Column to Existing Table

Task: Add a "phone" column to the library_books table you created earlier.

Output will appear here...

Guided Exercise 4: Design a Normalized Schema

Task: Create a simple normalized schema for a school with students, classes, and enrollments.

Output will appear here...

Part 2: Independent Practice

Challenge 1: Employee Management System

Difficulty: Medium

Task: Create an "employees_new" table with: emp_id (PK, autoinc), full_name (required), email (required, unique), hire_date (text), salary (real, must be >= 0), is_active (default 1).

Output will appear here...

Challenge 2: Blog Platform

Difficulty: Medium

Task: Design a normalized schema for a blog with: authors table (author_id, name, email, bio), posts table (post_id, author_id, title, content, published_date, view_count). Include proper constraints and foreign keys.

Output will appear here...

Challenge 3: Music Streaming Service

Difficulty: Hard

Task: Create a complete schema for a music streaming service with: artists (artist_id, name, country), albums (album_id, artist_id, title, release_year), songs (song_id, album_id, title, duration_seconds), playlists (playlist_id, user_id, name), playlist_songs (junction table). Include all necessary constraints.

Output will appear here...

Challenge 4: Restaurant Reservation System

Difficulty: Medium

Task: Create tables for: customers (customer_id, name, phone, email), tables (table_id, table_number, capacity), reservations (reservation_id, customer_id, table_id, reservation_date, party_size, status). Status must be one of: 'Pending', 'Confirmed', 'Completed', 'Cancelled'.

Output will appear here...

Challenge 5: E-Learning Platform

Difficulty: Hard

Task: Design a complete schema for an e-learning platform with: instructors, courses (linked to instructors), lessons (linked to courses), students, and enrollments (students enrolled in courses with progress tracking). Include constraints for: course price >= 0, lesson order numbers, enrollment date defaults.

Output will appear here...

📝 Knowledge Check Quiz

1. Which constraint ensures a column cannot be empty?

2. What data type should you use for prices like $19.99?

3. What does AUTOINCREMENT do?

4. What does the DEFAULT constraint do?

5. Which command adds a new column to an existing table?

6. What is database normalization?

7. What does a CHECK constraint do?

8. What happens when you execute DROP TABLE?