SQLMentor // learn sql

DDL โ€” Data Definition Language

DDL commands define and manage the structure of database objects. Where DML commands (INSERT, UPDATE, DELETE) work with data inside tables, DDL commands work with the tables, columns, indexes, and schemas themselves โ€” the containers that hold the data.

The four main DDL commands are:

Command Purpose
CREATE Build a new object (table, index, view, schema)
ALTER Modify an existing object's structure
DROP Permanently remove an object
TRUNCATE Remove all rows from a table (but keep the structure)
โš 
DDL commands in most databases are auto-committed โ€” they cannot be rolled back. DROP TABLE, for example, is permanent. Always back up important data before running destructive DDL in production.

CREATE TABLE

CREATE TABLE defines a new table: its name, columns, data types, and constraints.

Basic Syntax

CREATE TABLE table_name (
    column1_name  datatype  [constraints],
    column2_name  datatype  [constraints],
    ...
    [table_level_constraints]
);

Your First Table

CREATE TABLE employees (
    emp_id      INT              PRIMARY KEY,
    name        VARCHAR(100)     NOT NULL,
    email       VARCHAR(200)     UNIQUE,
    salary      DECIMAL(10,2)    CHECK (salary > 0),
    hire_date   DATE             DEFAULT CURRENT_DATE,
    dept_id     INT,
    is_active   BOOLEAN          DEFAULT TRUE
);

Inline vs Out-of-Line Constraints

Inline (column-level) โ€” constraint is written on the same line as the column:

CREATE TABLE products (
    product_id  INT          PRIMARY KEY,           -- inline
    name        VARCHAR(200) NOT NULL,              -- inline
    price       DECIMAL(8,2) CHECK (price >= 0),    -- inline
    category_id INT          REFERENCES categories  -- inline FK
);

Out-of-line (table-level) โ€” constraint is written separately, after all columns. Required for composite (multi-column) constraints:

CREATE TABLE order_items (
    order_id    INT         NOT NULL,
    product_id  INT         NOT NULL,
    quantity    INT         DEFAULT 1,
    unit_price  DECIMAL(10,2) NOT NULL,
    -- Out-of-line constraints:
    CONSTRAINT pk_order_items    PRIMARY KEY (order_id, product_id),
    CONSTRAINT fk_oi_order       FOREIGN KEY (order_id)    REFERENCES orders(order_id),
    CONSTRAINT fk_oi_product     FOREIGN KEY (product_id)  REFERENCES products(product_id),
    CONSTRAINT chk_qty_positive  CHECK (quantity > 0)
);

Data Types โ€” Comprehensive Reference

Choosing the right data type for each column is one of the most important design decisions. Using the wrong type wastes storage, causes implicit conversions that slow down queries, and allows invalid data.

Integer Types

Type Range Storage Use when
TINYINT -128 to 127 (or 0โ€“255) 1 byte Flags, small codes
SMALLINT -32,768 to 32,767 2 bytes Age, small counts
INT / INTEGER -2.1B to 2.1B 4 bytes IDs, counts, most integers
BIGINT ยฑ9.2 quintillion 8 bytes Large IDs, financial totals
NUMBER(p) (Oracle) Arbitrary precision Variable Oracle's general-purpose integer

Decimal / Exact Numeric Types

Type Description Example
DECIMAL(p, s) Exact decimal. p = total digits, s = digits after decimal DECIMAL(10, 2) โ†’ 12345678.99
NUMERIC(p, s) Identical to DECIMAL in most databases NUMERIC(8, 2)
NUMBER(p, s) (Oracle) Oracle's all-purpose exact numeric NUMBER(10, 2)
FLOAT / REAL Approximate โ€” beware rounding errors Not for money
DOUBLE PRECISION Higher-precision approximate Scientific data
โš 
Never use FLOAT or DOUBLE for monetary amounts. Floating-point numbers cannot represent some decimals exactly, leading to rounding errors like 0.1 + 0.2 = 0.30000000000000004. Always use DECIMAL or NUMERIC for money.

Character / String Types

Type Description Notes
VARCHAR(n) Variable-length string, up to n characters Standard; use for most text
VARCHAR2(n) (Oracle) Oracle's VARCHAR โ€” use this, not VARCHAR, in Oracle Prefer over Oracle's VARCHAR
CHAR(n) Fixed-length โ€” always n characters, padded with spaces Use for fixed-length codes: 'US', 'GB', 'M', 'F'
TEXT (PG/MySQL) Unlimited-length text Long descriptions, article bodies
CLOB (Oracle) Character Large Object โ€” large text Oracle's equivalent of TEXT
NVARCHAR(n) Unicode variable-length (SQL Server) Multilingual text

VARCHAR vs CHAR:

-- CHAR(5) always stores exactly 5 characters
-- 'US' stored as 'US   ' (padded with 3 spaces)
country_code  CHAR(2)      -- Good for ISO country codes ('US', 'GB', 'DE')
gender        CHAR(1)      -- Good for single-character codes ('M', 'F', 'O')

-- VARCHAR(100) stores only as many characters as needed
-- 'Alice' stored as 'Alice' (5 characters, not padded)
name          VARCHAR(100) -- Good for variable-length names
email         VARCHAR(200) -- Good for email addresses

Date and Time Types

Type Stores Oracle PostgreSQL MySQL
DATE Date (and time in Oracle) date + time date only date only
TIME Time of day โ€” โœ“ โœ“
TIMESTAMP Date + time with fractional seconds โœ“ โœ“ โœ“
TIMESTAMP WITH TIME ZONE Timestamp + timezone offset โœ“ โœ“ Limited
INTERVAL Duration (e.g., 3 months, 2 days) โœ“ โœ“ Limited
โ„น
In Oracle, DATE stores both date AND time (to the second). In PostgreSQL and MySQL, DATE stores only the date. Use TIMESTAMP when you need both date and time across all databases.

Boolean Type

Database Boolean type
PostgreSQL BOOLEAN โ€” stores TRUE / FALSE / NULL
MySQL BOOLEAN (alias for TINYINT(1)) โ€” 1=true, 0=false
SQL Server BIT โ€” 1=true, 0=false
Oracle No native BOOLEAN for tables โ€” use NUMBER(1) CHECK (col IN (0,1)) or CHAR(1) CHECK (col IN ('Y','N'))
-- PostgreSQL / MySQL
is_active  BOOLEAN  DEFAULT TRUE

-- Oracle workaround
is_active  NUMBER(1)  DEFAULT 1  CHECK (is_active IN (0, 1))
-- or
is_active  CHAR(1)    DEFAULT 'Y' CHECK (is_active IN ('Y', 'N'))

Large Object Types (LOBs)

For very large content (documents, images, binary files):

Type Purpose Database
CLOB Character Large Object โ€” large text Oracle, DB2
BLOB Binary Large Object โ€” images, PDFs, files Oracle, MySQL
TEXT Long text (effectively unlimited) PostgreSQL, MySQL
BYTEA Binary data PostgreSQL
VARBINARY(MAX) Binary data SQL Server
CREATE TABLE documents (
    doc_id    INT   PRIMARY KEY,
    title     VARCHAR(500) NOT NULL,
    body      CLOB,           -- Oracle: large text content
    attachment BLOB,          -- Oracle: binary file
    thumbnail  BLOB           -- Oracle: binary image
);

Data Type Comparison Across Databases

Concept Oracle MySQL PostgreSQL SQL Server
Auto-increment ID NUMBER + SEQUENCE or GENERATED ALWAYS AS IDENTITY INT AUTO_INCREMENT SERIAL or INT GENERATED ALWAYS AS IDENTITY INT IDENTITY(1,1)
Variable text VARCHAR2(n) VARCHAR(n) VARCHAR(n) VARCHAR(n) or NVARCHAR(n)
Large text CLOB TEXT TEXT VARCHAR(MAX)
Boolean NUMBER(1) (workaround) BOOLEAN / TINYINT(1) BOOLEAN BIT
Date + Time DATE (includes time) DATETIME / TIMESTAMP TIMESTAMP DATETIME2
Current timestamp SYSDATE NOW() NOW() / CURRENT_TIMESTAMP GETDATE()

Auto-Increment Primary Keys

A very common pattern is a primary key that automatically increments with each new row:

-- MySQL / MariaDB
CREATE TABLE users (
    user_id  INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

-- PostgreSQL: SERIAL shorthand
CREATE TABLE users (
    user_id  SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

-- PostgreSQL / SQL Server / Oracle 12c+: IDENTITY column
CREATE TABLE users (
    user_id  INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

-- Oracle pre-12c: use a SEQUENCE + trigger
CREATE SEQUENCE seq_users START WITH 1 INCREMENT BY 1;
CREATE TABLE users (
    user_id  INT DEFAULT seq_users.NEXTVAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

CREATE TABLE AS SELECT (CTAS)

CTAS creates a new table and populates it with the result of a SELECT query. It is useful for:

  • Creating summary/report tables
  • Making a backup copy of data
  • Creating a subset of a large table for testing
-- Create a new table with all IT department employees
CREATE TABLE it_employees AS
SELECT *
FROM   employees
WHERE  dept = 'IT';

-- Create a summary table (aggregated data)
CREATE TABLE dept_summary AS
SELECT dept,
       COUNT(*)        AS headcount,
       AVG(salary)     AS avg_salary,
       SUM(salary)     AS total_payroll
FROM   employees
GROUP BY dept;

-- Copy table structure WITHOUT data (using an always-false WHERE)
CREATE TABLE employees_backup AS
SELECT * FROM employees WHERE 1 = 0;
-- Result: empty table with the same columns and types as employees
-- Note: constraints are NOT copied (no PRIMARY KEY, FOREIGN KEY, etc.)
โš 
CTAS copies column names and data types but does NOT copy constraints (no PRIMARY KEY, FOREIGN KEY, UNIQUE, NOT NULL, CHECK). You must add constraints separately with ALTER TABLE after the CTAS.

ALTER TABLE โ€” Modifying Existing Tables

ALTER TABLE lets you change a table's structure after it has been created โ€” without dropping and recreating it (and losing all the data).

Adding Columns

-- Add a single column
ALTER TABLE employees ADD phone VARCHAR(20);

-- Add a column with a default and constraint
ALTER TABLE employees ADD bonus DECIMAL(10,2) DEFAULT 0 CHECK (bonus >= 0);

-- Add multiple columns (MySQL / Oracle)
ALTER TABLE employees
ADD hire_date DATE,
ADD department_notes TEXT;

-- Add multiple columns (PostgreSQL)
ALTER TABLE employees
ADD COLUMN manager_id INT,
ADD COLUMN performance_rating INT CHECK (performance_rating BETWEEN 1 AND 5);

Modifying Columns

-- Change a column's data type (Oracle / MySQL)
ALTER TABLE employees MODIFY salary NUMBER(12, 2);

-- Change a column's data type (PostgreSQL / SQL Server)
ALTER TABLE employees ALTER COLUMN salary TYPE NUMERIC(12, 2);

-- Increase VARCHAR length (safe โ€” won't lose data)
ALTER TABLE employees MODIFY name VARCHAR(200);   -- Oracle
ALTER TABLE employees ALTER COLUMN name TYPE VARCHAR(200);  -- PostgreSQL

-- Decreasing length is risky โ€” only works if no existing values exceed the new limit

-- Add NOT NULL (column must have no NULLs first)
ALTER TABLE employees MODIFY salary NOT NULL;   -- Oracle
ALTER TABLE employees ALTER COLUMN salary SET NOT NULL;  -- PostgreSQL

Renaming Columns

-- Oracle / PostgreSQL / MySQL 8+
ALTER TABLE employees RENAME COLUMN phone TO phone_number;

-- SQL Server
EXEC sp_rename 'employees.phone', 'phone_number', 'COLUMN';

Dropping Columns

-- Remove a column permanently (data is gone!)
ALTER TABLE employees DROP COLUMN notes;

-- Drop multiple columns (MySQL)
ALTER TABLE employees DROP COLUMN notes, DROP COLUMN temp_code;

Renaming a Table

-- Oracle
ALTER TABLE products RENAME TO inventory;

-- PostgreSQL
ALTER TABLE products RENAME TO inventory;

-- MySQL
RENAME TABLE products TO inventory;

-- SQL Server
EXEC sp_rename 'products', 'inventory';

Adding and Dropping Constraints via ALTER TABLE

-- Add a PRIMARY KEY
ALTER TABLE employees
ADD CONSTRAINT pk_employees PRIMARY KEY (emp_id);

-- Add a FOREIGN KEY
ALTER TABLE employees
ADD CONSTRAINT fk_emp_dept
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
    ON DELETE SET NULL;

-- Add a CHECK constraint
ALTER TABLE products
ADD CONSTRAINT chk_price CHECK (price >= 0);

-- Add a UNIQUE constraint
ALTER TABLE users
ADD CONSTRAINT uq_users_email UNIQUE (email);

-- Drop any named constraint
ALTER TABLE employees DROP CONSTRAINT fk_emp_dept;
ALTER TABLE products   DROP CONSTRAINT chk_price;
ALTER TABLE users      DROP CONSTRAINT uq_users_email;

-- Drop a PRIMARY KEY
ALTER TABLE employees DROP CONSTRAINT pk_employees;

DROP TABLE โ€” Removing a Table Permanently

DROP TABLE removes the entire table โ€” structure and all data โ€” permanently.

-- Drop the table (will error if it does not exist)
DROP TABLE old_archive;

-- Drop only if it exists (prevents error in scripts)
DROP TABLE IF EXISTS old_archive;    -- PostgreSQL, MySQL, SQL Server

-- Oracle: does not have IF EXISTS, use a procedure or catch the error

-- Drop with CASCADE to remove dependent objects (views, FK references)
DROP TABLE departments CASCADE CONSTRAINTS;  -- Oracle
DROP TABLE departments CASCADE;              -- PostgreSQL
โš 
DROP TABLE is irreversible. There is no undo. In Oracle, dropped tables go to the "recycle bin" (SHOW RECYCLEBIN), and can be recovered with FLASHBACK TABLE table_name TO BEFORE DROP โ€” but only until the recycle bin is purged. Always confirm the table name before running DROP in production.

TRUNCATE TABLE โ€” Removing All Rows

TRUNCATE removes all rows from a table but keeps the table structure intact. It is much faster than DELETE FROM table for clearing large tables because it does not log individual row deletions.

-- Remove all rows (structure remains)
TRUNCATE TABLE temp_staging;

-- Oracle variant
TRUNCATE TABLE temp_staging REUSE STORAGE;  -- keeps allocated disk space

-- PostgreSQL: can truncate multiple tables and cascade
TRUNCATE TABLE orders, order_items RESTART IDENTITY CASCADE;

TRUNCATE vs DELETE vs DROP

Command Removes structure? Removes data? Rollback? Speed
DELETE FROM t No Filtered rows (can use WHERE) Yes (DML) Slow โ€” logged per row
TRUNCATE TABLE t No All rows No (DDL, auto-committed) Fast โ€” minimal logging
DROP TABLE t Yes All data No Fast
-- DELETE: can be rolled back, can have WHERE clause
BEGIN;
DELETE FROM employees WHERE dept = 'IT';
ROLLBACK;   -- IT employees are back!

-- TRUNCATE: cannot be rolled back (in most databases), no WHERE
TRUNCATE TABLE employees;
-- All rows gone, cannot roll back, but structure remains

-- DROP: everything gone
DROP TABLE employees;
-- Table no longer exists
โš 
In Oracle, TRUNCATE is DDL and cannot be rolled back. In PostgreSQL, TRUNCATE can be rolled back if it is inside an explicit transaction. Always check your database's behaviour before assuming.

Table Design Best Practices

Naming Conventions

-- Tables: plural nouns, lowercase, underscore-separated
employees     orders     order_items    product_categories

-- Columns: singular nouns, lowercase, underscore-separated
emp_id        first_name    order_date    is_active

-- Primary keys: tablename_id (or just id)
emp_id        order_id      product_id

-- Foreign keys: match the referenced primary key's name
-- employees.dept_id references departments.dept_id (same name)

-- Boolean columns: start with is_, has_, can_
is_active     has_children    can_edit

-- Timestamp columns: end with _at or _date
created_at    updated_at    order_date    birth_date

Surrogate vs Natural Keys

Surrogate key โ€” a system-generated ID (usually an auto-increment integer or UUID) that has no business meaning:

emp_id  INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY

Natural key โ€” a real-world identifier that is meaningful to the business:

passport_number  CHAR(9)  PRIMARY KEY
-- OR
ssn              CHAR(11) PRIMARY KEY

Recommendation: Use surrogate keys for almost everything. Natural keys seem convenient but:

  • Passport numbers can change (renewal)
  • SSNs are sensitive data that should not be used as join keys
  • Natural keys from external systems are not under your control
-- Good table design with surrogate key
CREATE TABLE employees (
    emp_id          INT              GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    employee_number VARCHAR(20)      UNIQUE NOT NULL,   -- natural key as UNIQUE, not PK
    first_name      VARCHAR(100)     NOT NULL,
    last_name       VARCHAR(100)     NOT NULL,
    email           VARCHAR(200)     UNIQUE NOT NULL,
    salary          DECIMAL(12, 2)   NOT NULL CHECK (salary > 0),
    hire_date       DATE             NOT NULL DEFAULT CURRENT_DATE,
    dept_id         INT              REFERENCES departments(dept_id) ON DELETE SET NULL,
    is_active       BOOLEAN          NOT NULL DEFAULT TRUE,
    created_at      TIMESTAMP        NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP        NOT NULL DEFAULT CURRENT_TIMESTAMP
);

Choosing Column Data Types

-- IDs and counts: use INT (or BIGINT for very large tables)
emp_id          INT
order_count     INT

-- Money: always use DECIMAL/NUMERIC, never FLOAT
price           DECIMAL(10, 2)
tax_amount      DECIMAL(12, 4)  -- more precision for tax

-- Short text: VARCHAR with a reasonable maximum
name            VARCHAR(200)
email           VARCHAR(254)   -- 254 is the official max email length per RFC 5321
phone           VARCHAR(20)    -- include country code, brackets, dashes

-- Fixed-length codes: use CHAR
country_code    CHAR(2)        -- ISO 3166: 'US', 'GB', 'DE'
currency_code   CHAR(3)        -- ISO 4217: 'USD', 'EUR', 'GBP'
gender          CHAR(1)        -- 'M', 'F', 'O', 'U'

-- Dates: DATE for dates, TIMESTAMP for date+time
birth_date      DATE
order_placed_at TIMESTAMP

-- Large text: TEXT or CLOB for unlimited content
description     TEXT
article_body    TEXT

Common Errors

Error Cause Fix
ORA-00955 Name already used by an existing object โ€” table, index, or view already exists with that name Use CREATE OR REPLACE (views/synonyms) or drop the existing object first; check USER_OBJECTS
ORA-01430 Column being added already exists in table โ€” duplicate column name in ALTER TABLE ADD Check USER_TAB_COLUMNS before adding; rename the column if both should exist
ORA-02292 Integrity constraint violated โ€” child records exist when trying to DROP or TRUNCATE a parent table Drop or disable FK constraints first; or use DROP TABLE โ€ฆ CASCADE CONSTRAINTS
ORA-00054 Resource busy and acquire with NOWAIT โ€” DDL requires a table lock but another session holds it Wait for active transactions to commit/rollback; use ALTER SYSTEM KILL SESSION as last resort
ORA-01031 Insufficient privileges โ€” user does not have CREATE TABLE / CREATE INDEX / etc. Ask DBA to grant the required privilege; GRANT CREATE TABLE TO username
ORA-14400 Inserted partition key does not map to any partition โ€” value falls outside defined partition ranges Add a DEFAULT (MAXVALUE) partition or extend range partition boundaries

Interview Corner

IQ ยท DDL
What is the difference between TRUNCATE and DELETE when removing all rows from a table?
โ–ถ Show answer
Feature TRUNCATE DELETE
SQL category DDL DML
Transaction log Minimal (no row-level undo) Full row-level undo logged
Can roll back No (auto-commit in Oracle) Yes, until COMMIT
Triggers fired No Yes (row-level triggers fire)
WHERE clause Not supported Supported
Speed Very fast (resets HWM) Slow on large tables
Resets sequences No (use ALTER SEQUENCE โ€ฆ RESTART) No

Use TRUNCATE to quickly empty a table during ETL or testing. Use DELETE when you need to roll back, fire triggers, or remove a subset of rows.

IQ ยท DDL
What happens to dependent objects (views, procedures, synonyms) when you ALTER TABLE and remove or rename a column?
โ–ถ Show answer

Oracle marks dependent objects as INVALID automatically. They remain in the database but will fail when called until they are recompiled or recreated.

ALTER TABLE employees DROP COLUMN middle_name;

-- Any view or procedure referencing MIDDLE_NAME is now INVALID
SELECT object_name, object_type, status
FROM   user_objects
WHERE  status = 'INVALID';

Resolution:

  • For views: CREATE OR REPLACE VIEW โ€ฆ to redefine without the dropped column.
  • For stored procedures/packages: ALTER PROCEDURE proc_name COMPILE;
  • Oracle will automatically attempt to recompile invalid objects the next time they are called (lazy recompilation), but it's better to recompile proactively.

Related Topics

  • Constraints โ€” constraints defined alongside or after table creation
  • DML Commands โ€” INSERT, UPDATE, DELETE, MERGE to manipulate the data in created tables
  • Indexes โ€” index creation and maintenance (CREATE INDEX, REBUILD)
  • Sequences & Synonyms โ€” auto-increment sequences and name aliases
  • Data Dictionary โ€” USER_TABLES, USER_TAB_COLUMNS, USER_OBJECTS to inspect schema