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
- What DDL is and how it differs from DML
- How to define tables using CREATE TABLE
- How to specify column names and data types
- How to add constraints at creation time
- How to create relationships with foreign keys
- Common data types and when to use each
- How to validate table structure after creation
- Best practices for table creation
- Complete examples of real-world tables
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:
CREATE TABLE students- Creates a new table named "students"student_id INT- Column named student_id, data type INTPRIMARY KEY- Makes student_id the primary keyAUTO_INCREMENT- Automatically assigns next IDNOT NULL- Column must have a valueUNIQUE- No duplicate values allowedDEFAULT CURRENT_DATE- Uses today's date if not provided
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:
- β Auto-incrementing primary key
- β Required fields (NOT NULL)
- β Unique constraint (SKU)
- β Appropriate data types (DECIMAL for money, BOOLEAN for flags)
- β DEFAULT values for common fields
- β Automatic timestamps (created_at, updated_at)
- β Named CHECK constraints for validation
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
- DDL (Data Definition Language) defines database structure
- CREATE TABLE is the core DDL command for defining tables
- Always specify appropriate data types for columns
- Use DECIMAL for money, never FLOAT or DOUBLE
- Include primary keys in every table (usually AUTO_INCREMENT)
- Add NOT NULL to required fields
- Add UNIQUE to fields that must be distinct
- Create parent tables before child tables
- Use named constraints (CONSTRAINT name ...) for clarity
- Include timestamps (created_at, updated_at) in most tables
- Add CHECK constraints for validation at database level
- Use DESCRIBE or SHOW COLUMNS to verify structure
- Follow naming conventions (lowercase, underscores)