-- Table created with auto-increment ID, constraints, and defaults
Note SERIAL is PostgreSQL-specific (auto-increment integer). MySQL uses INT AUTO_INCREMENT. Standard SQL uses GENERATED ALWAYS AS IDENTITY. Always define a primary key.
Note Use DECIMAL for money - never FLOAT or DOUBLE, which have rounding errors. Use TEXT over VARCHAR when you do not need a length limit. JSONB (PostgreSQL) is preferred over JSON because it supports indexing.
data typescolumn typesvarchar vs textinteger typesdecimal vs float
PRIMARY KEY
Syntax
column_name type PRIMARYKEY-- or composite:PRIMARYKEY(col1, col2)
-- Composite primary key on (order_id, product_id)
Note A primary key is automatically NOT NULL and UNIQUE. Composite primary keys are useful for junction/bridge tables. Each table should have exactly one primary key.
Note ON DELETE options: RESTRICT (block), CASCADE (delete child rows), SET NULL, SET DEFAULT. CASCADE is convenient but dangerous - one delete can wipe many related rows. Default is RESTRICT in most databases.
column_name type UNIQUE-- or table-level:UNIQUE(col1, col2)
Example
CREATETABLEemployees(
id SERIALPRIMARYKEY,
employee_number VARCHAR(20)NOTNULLUNIQUE,
email VARCHAR(255)NOTNULL,
department_id INTEGERNOTNULL,UNIQUE(email, department_id));
Output
-- employee_number is unique on its own;
-- (email, department_id) must be unique as a pair
Note UNIQUE allows multiple NULLs in most databases (PostgreSQL, MySQL). SQL Server treats NULLs as equal in unique constraints by default, so only one NULL is allowed. Use a partial unique index to handle this.
-- Ensures max_attendees is positive and end_date is not before start_date
Note MySQL 8.0+ supports CHECK constraints (earlier versions parsed but silently ignored them). CHECK constraints cannot reference other tables - use triggers or application logic for cross-table validation.
CREATETABLEtasks(
id SERIALPRIMARYKEY,
title VARCHAR(200)NOTNULL,
status VARCHAR(20)DEFAULT'pending',
priority INTEGERDEFAULT0,
created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP);
Output
-- Omitting status, priority, or created_at uses defaults
Note DEFAULT applies only when the column is omitted from INSERT. Explicitly inserting NULL overrides the default with NULL (unless NOT NULL is also set). You can use expressions like CURRENT_TIMESTAMP as defaults.
Note ALTER TABLE syntax varies across databases. PostgreSQL uses ALTER COLUMN ... TYPE. MySQL uses MODIFY COLUMN. Adding NOT NULL to a column with existing NULLs will fail - update the data first.
alter tableadd columndrop columnmodify columnchange tablerename column
Note DROP TABLE is irreversible outside of a transaction. CASCADE also drops views, foreign keys, and other objects that depend on the table. MySQL does not support CASCADE on DROP TABLE the same way.
-- Creates the table only if it does not already exist
Note IF NOT EXISTS prevents errors in migration scripts that might run multiple times. It does NOT verify that the existing table has the same schema - it just skips creation if the name exists.
create if not existsidempotent createsafe create table