← Back to Lessons
BEGINNER πŸ”§ DDL ⏱ 40 minutes

Creating Tables with DDL: Defining Database Structure

Now that you understand constraints, it's time to put everything together and create complete, production-ready tables. DDL (Data Definition Language) is the subset of SQL used to define database structureβ€”tables, columns, data types, and constraints. The CREATE TABLE statement is where database design becomes reality. In this lesson, you'll learn how to translate your design into SQL, choose appropriate data types, add all necessary constraints, and validate your table structure.


πŸ“š What You'll Learn

1. What is DDL (Data Definition Language)?

DDL (Data Definition Language) is the part of SQL that defines the structure of your database. It creates, modifies, and deletes database objects like tables, indexes, and views.

DDL vs. DML vs. DQL

Category Purpose Commands
DDL
Data Definition Language
Define database structure CREATE, ALTER, DROP, TRUNCATE
DML
Data Manipulation Language
Manipulate data within tables INSERT, UPDATE, DELETE
DQL
Data Query Language
Retrieve data from tables SELECT

In this lesson, we focus on CREATE TABLE (DDL). Later lessons cover DML (INSERT, UPDATE, DELETE) and you've already learned DQL (SELECT).

2. Basic CREATE TABLE Syntax

CREATE TABLE table_name (
    column1_name datatype constraints,
    column2_name datatype constraints,
    column3_name datatype constraints,
    table_constraints
);

Simple Example: Students Table

CREATE TABLE students (
    student_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    enrollment_date DATE NOT NULL DEFAULT CURRENT_DATE
);

Breakdown:

3. Choosing the Right Data Types

Numeric Data Types

Type Range Use Case
INT -2 billion to +2 billion IDs, counts, years
BIGINT Very large integers Large IDs, populations
DECIMAL(p,s) Exact precision Money, prices (e.g., DECIMAL(10,2))
FLOAT/DOUBLE Approximate Scientific data (not money!)

String Data Types

Type Max Length Use Case
VARCHAR(n) Up to n chars (max 65,535) Names, emails, addresses
CHAR(n) Fixed n chars Codes (e.g., CHAR(2) for state)
TEXT Up to 65,535 chars Long content, descriptions
MEDIUMTEXT Up to 16 million chars Articles, blog posts

Date and Time Types

Type Format Use Case
DATE YYYY-MM-DD Birth dates, event dates
TIME HH:MM:SS Start time, duration
DATETIME YYYY-MM-DD HH:MM:SS Exact moments (no timezone)
TIMESTAMP YYYY-MM-DD HH:MM:SS Created/updated timestamps

Boolean and Other Types

Type Description Use Case
BOOLEAN TRUE or FALSE (stored as 0/1) Flags (is_active, is_verified)
ENUM('a','b','c') Limited set of values Status fields (alternative to CHECK)

4. Complete Table with All Constraints

Example: E-Commerce Products Table

CREATE TABLE products (
    -- Primary key
    product_id INT PRIMARY KEY AUTO_INCREMENT,
    
    -- Basic information
    name VARCHAR(255) NOT NULL,
    sku VARCHAR(50) NOT NULL UNIQUE,
    description TEXT,
    
    -- Pricing
    price DECIMAL(10, 2) NOT NULL,
    discount_percent INT DEFAULT 0,
    
    -- Inventory
    stock_quantity INT DEFAULT 0,
    is_available BOOLEAN DEFAULT TRUE,
    
    -- Timestamps
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    -- Constraints
    CONSTRAINT chk_positive_price CHECK (price > 0),
    CONSTRAINT chk_valid_discount CHECK (discount_percent BETWEEN 0 AND 100),
    CONSTRAINT chk_valid_stock CHECK (stock_quantity >= 0)
);

This table demonstrates:

5. Creating Related Tables with Foreign Keys

When creating tables with relationships, always create parent tables before child tables.

Example: Blog System

Step 1: Create Parent Tables First

-- Parent table: authors
CREATE TABLE authors (
    author_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    bio TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Parent table: categories
CREATE TABLE categories (
    category_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL UNIQUE,
    description TEXT
);

Step 2: Create Child Table with Foreign Keys

-- Child table: posts (references both authors and categories)
CREATE TABLE posts (
    post_id INT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(255) NOT NULL,
    content TEXT NOT NULL,
    
    -- Foreign keys
    author_id INT NOT NULL,
    category_id INT,
    
    -- Status and timestamps
    status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
    views INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    
    -- Foreign key constraints
    CONSTRAINT fk_post_author 
        FOREIGN KEY (author_id) REFERENCES authors(author_id),
    
    CONSTRAINT fk_post_category 
        FOREIGN KEY (category_id) REFERENCES categories(category_id)
        ON DELETE SET NULL,
    
    -- Validation constraints
    CONSTRAINT chk_valid_views CHECK (views >= 0)
);

πŸ’‘ Note: The posts table uses ON DELETE SET NULL for category_id. This means if a category is deleted, the post remains but category_id becomes NULL. For author_id, we don't specify ON DELETE, so the default (RESTRICT) prevents deleting an author who has posts.

6. Validating Table Structure

View Table Structure

-- Show table structure
DESCRIBE products;
-- or
SHOW COLUMNS FROM products;

-- Show CREATE TABLE statement
SHOW CREATE TABLE products;

Sample Output

+------------------+---------------+------+-----+-------------------+
| Field            | Type          | Null | Key | Default           |
+------------------+---------------+------+-----+-------------------+
| product_id       | int           | NO   | PRI | NULL              |
| name             | varchar(255)  | NO   |     | NULL              |
| sku              | varchar(50)   | NO   | UNI | NULL              |
| price            | decimal(10,2) | NO   |     | NULL              |
| stock_quantity   | int           | YES  |     | 0                 |
| created_at       | timestamp     | YES  |     | CURRENT_TIMESTAMP |
+------------------+---------------+------+-----+-------------------+

List All Tables in Database

-- Show all tables
SHOW TABLES;

-- Show tables with pattern
SHOW TABLES LIKE 'product%';

7. Table Creation Best Practices

βœ… DO:

  • Use lowercase with underscores for table names (products, order_items)
  • Always include a primary key (usually id or table_id)
  • Use appropriate data types (DECIMAL for money, not FLOAT)
  • Add NOT NULL to required fields
  • Add UNIQUE to fields that must be distinct
  • Name foreign key constraints (fk_child_parent)
  • Include created_at and updated_at timestamps
  • Add CHECK constraints for validation
  • Document complex constraints with comments

❌ DON'T:

  • Use spaces or special characters in table names
  • Create tables without primary keys
  • Use FLOAT/DOUBLE for monetary values
  • Make everything VARCHAR(255) without thinking
  • Forget NOT NULL on required fields
  • Create child tables before parent tables
  • Skip validation constraints (rely on app only)
  • Use ambiguous names (data, info, temp)

8. 🎯 Practice Exercises

Exercise 1: Create a Customers Table

Create a customers table with:

  • Auto-incrementing primary key (customer_id)
  • First name and last name (both required)
  • Email (required, unique)
  • Phone (optional, unique if provided)
  • Date of birth (required)
  • Account status (default 'active', must be: active, suspended, closed)
  • Created timestamp
  • Constraint: must be 18 or older (check date of birth)
Show Solution
CREATE TABLE customers (
    customer_id INT PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    phone VARCHAR(20) UNIQUE,
    date_of_birth DATE NOT NULL,
    account_status ENUM('active', 'suspended', 'closed') DEFAULT 'active',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    
    -- Age validation (18 years ago from today)
    CONSTRAINT chk_minimum_age CHECK (
        date_of_birth <= DATE_SUB(CURRENT_DATE, INTERVAL 18 YEAR)
    )
);

-- Verify structure
DESCRIBE customers;

-- Test insert
INSERT INTO customers (first_name, last_name, email, date_of_birth)
VALUES ('Alice', 'Johnson', 'alice@example.com', '1995-05-15');

Key Points:

  • βœ… ENUM for status (alternative to CHECK with IN)
  • βœ… Phone is UNIQUE but nullable (optional but no duplicates)
  • βœ… DATE_SUB calculates date 18 years ago
  • βœ… All required fields have NOT NULL

Exercise 2: Create Orders and Order Items Tables

Create an order system with proper relationships. Remember to create tables in the correct order!

orders table:

  • order_id (PK)
  • customer_id (FK to customers, required)
  • order_date (default today)
  • total_amount (required, must be positive)
  • status (default 'pending', must be: pending, processing, shipped, delivered, cancelled)

order_items table:

  • order_item_id (PK)
  • order_id (FK to orders, required)
  • product_id (FK to products, required)
  • quantity (required, must be positive)
  • unit_price (required, must be positive)
Show Solution
-- Parent table: orders
CREATE TABLE orders (
    order_id INT PRIMARY KEY AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date DATE DEFAULT CURRENT_DATE,
    total_amount DECIMAL(10, 2) NOT NULL,
    status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled') 
        DEFAULT 'pending',
    
    -- Foreign key to customers
    CONSTRAINT fk_order_customer
        FOREIGN KEY (customer_id) REFERENCES customers(customer_id),
    
    -- Validation
    CONSTRAINT chk_positive_total CHECK (total_amount > 0)
);

-- Child table: order_items
CREATE TABLE order_items (
    order_item_id INT PRIMARY KEY AUTO_INCREMENT,
    order_id INT NOT NULL,
    product_id INT NOT NULL,
    quantity INT NOT NULL,
    unit_price DECIMAL(10, 2) NOT NULL,
    
    -- Foreign keys
    CONSTRAINT fk_item_order
        FOREIGN KEY (order_id) REFERENCES orders(order_id)
        ON DELETE CASCADE,  -- If order deleted, delete items too
    
    CONSTRAINT fk_item_product
        FOREIGN KEY (product_id) REFERENCES products(product_id),
    
    -- Validation
    CONSTRAINT chk_positive_quantity CHECK (quantity > 0),
    CONSTRAINT chk_positive_price CHECK (unit_price > 0)
);

-- Verify structures
DESCRIBE orders;
DESCRIBE order_items;

Design Decisions:

  • βœ… orders created before order_items (parent before child)
  • βœ… CASCADE on order_items: delete items with order
  • βœ… unit_price in order_items (not just product reference)
  • βœ… Preserves historical pricing
  • βœ… All monetary values use DECIMAL

πŸ”₯ Challenge: Complete University System

Create a complete university database with these tables:

  • departments: department_id, name, building, phone
  • professors: professor_id, name, email, department_id (FK), hire_date
  • courses: course_id, code (unique), title, credits (1-4), department_id (FK)
  • students: student_id, name, email, major (department_id FK), enrollment_year
  • enrollments: enrollment_id, student_id (FK), course_id (FK), professor_id (FK), semester, grade

Create all five tables with appropriate constraints and relationships. Think carefully about the order!

Show Solution
-- Step 1: Parent table with no dependencies
CREATE TABLE departments (
    department_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL UNIQUE,
    building VARCHAR(100),
    phone VARCHAR(20)
);

-- Step 2: Tables that reference departments
CREATE TABLE professors (
    professor_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    department_id INT NOT NULL,
    hire_date DATE NOT NULL,
    
    CONSTRAINT fk_prof_dept
        FOREIGN KEY (department_id) REFERENCES departments(department_id)
);

CREATE TABLE courses (
    course_id INT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(20) NOT NULL UNIQUE,
    title VARCHAR(255) NOT NULL,
    credits INT NOT NULL,
    department_id INT NOT NULL,
    
    CONSTRAINT fk_course_dept
        FOREIGN KEY (department_id) REFERENCES departments(department_id),
    
    CONSTRAINT chk_valid_credits CHECK (credits BETWEEN 1 AND 4)
);

CREATE TABLE students (
    student_id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE,
    major INT,  -- Optional major
    enrollment_year INT NOT NULL,
    
    CONSTRAINT fk_student_major
        FOREIGN KEY (major) REFERENCES departments(department_id),
    
    CONSTRAINT chk_valid_year CHECK (enrollment_year >= 1900)
);

-- Step 3: Junction table that references multiple parents
CREATE TABLE enrollments (
    enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    professor_id INT NOT NULL,
    semester VARCHAR(20) NOT NULL,
    grade VARCHAR(2),
    
    CONSTRAINT fk_enroll_student
        FOREIGN KEY (student_id) REFERENCES students(student_id),
    
    CONSTRAINT fk_enroll_course
        FOREIGN KEY (course_id) REFERENCES courses(course_id),
    
    CONSTRAINT fk_enroll_professor
        FOREIGN KEY (professor_id) REFERENCES professors(professor_id),
    
    -- Prevent duplicate enrollments
    UNIQUE (student_id, course_id, semester),
    
    -- Grade validation
    CONSTRAINT chk_valid_grade CHECK (
        grade IN ('A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D', 'F') 
        OR grade IS NULL
    )
);

-- Verify all tables
SHOW TABLES;
DESCRIBE enrollments;

Complex Relationships:

  • βœ… Correct creation order: departments β†’ (professors, courses, students) β†’ enrollments
  • βœ… enrollments references 3 other tables
  • βœ… Composite UNIQUE prevents duplicate enrollments in same semester
  • βœ… major is optional (students can be undeclared)
  • βœ… grade is optional (not yet assigned)
  • βœ… Real-world validation (credits 1-4, valid grades)

πŸ“ Key Takeaways