CREATE TABLE
Define a table — columns, types, and the constraints that keep data clean
Defining a Table
Up to now every query has read from tables that already exist. CREATE TABLE is how those tables come into being. You name the table, list the columns with their data types, and optionally attach constraints that enforce rules at write time. Here's what goes into a good table definition:
INTEGER, REAL (floating-point), TEXT, BLOB. SQLite is dynamically typed but still respects these declarations. Postgres and MySQL enforce them strictly.id INTEGER PRIMARY KEY in SQLite auto-increments when omitted from INSERT. SQLite quirk: for compatibility with code written in 2001 (back when SQLite had a NULL-in-PK bug), non-INTEGER PRIMARY KEY columns can still hold NULL unless you also write NOT NULL explicitly. INTEGER PRIMARY KEY is the only PK type that auto-rejects NULLs in SQLite. Postgres, MySQL, and SQL Server all reject NULL in any PK column.DEFAULT CURRENT_TIMESTAMP), status fields (DEFAULT 'pending'), and boolean flags (DEFAULT 0).salary REAL CHECK(salary >= 0) refuses negative salaries. rating INTEGER CHECK(rating BETWEEN 1 AND 5) restricts to 1–5. Pushes data-quality rules into the database where they can't be bypassed.FOREIGN KEY (customer_id) REFERENCES customers(id) prevents inserting orders for customers that don't exist. In SQLite, FK enforcement is off by default — turn it on with PRAGMA foreign_keys = ON.email TEXT UNIQUE prevents two rows from sharing the same email. Creates an implicit index.How CREATE TABLE Works
Declare columns, types, and constraints
- Each column has a name and a type (INTEGER, REAL, TEXT, BLOB)
- PRIMARY KEY uniquely identifies each row and is auto-indexed
- Constraints (NOT NULL, UNIQUE, CHECK, FK) run on every INSERT/UPDATE
- CREATE TABLE IF NOT EXISTS is safe to re-run
Common Constraints
What each one does
- PRIMARY KEY — unique + not null + indexed
- NOT NULL — refuses missing values
- UNIQUE — no two rows share this value
- DEFAULT expr — fills in when INSERT omits
- CHECK(cond) — row-level validation
- FOREIGN KEY (col) REFERENCES t(id) — referential integrity
Schema Traps
Common mistakes
- No primary key → can't reliably UPDATE/DELETE specific rows
- Storing numbers/dates as TEXT → broken sort and arithmetic
- SQLite FKs are OFF by default — turn on with PRAGMA foreign_keys = ON
- ALTER COLUMN type is not supported in SQLite — plan ahead
Type Picking Guide
Choosing column types
- INTEGER for counts, IDs, flags
- REAL for prices (NUMERIC(10,2) in Postgres)
- TEXT for strings and ISO dates
- Timestamps: TEXT in SQLite, TIMESTAMP in Postgres
PRIMARY KEYUnique row identifier; automatically indexedNOT NULLRefuses rows missing this valueDEFAULT 'x'Fills in a value when INSERT omits this columnCHECK(expr)Row-level validation — refuses rows where expr is falseFOREIGN KEYEnforces that this column references an existing row in another tableCREATE TABLE IF NOT EXISTSSafe re-run — no error if table already existsWorked Example
Define an employees table with constraints, then insert two rows and verify the schema.
id is the primary key, name cannot be null, email must be unique, department defaults to "Unassigned" if omitted, salary cannot be negative, and manager_id references another row in the same employees table (a self-referential foreign key — how org hierarchies are modeled). After INSERTing two rows, the SELECT verifies both stored correctly. Run each statement one at a time in the Practice Lab.| id | name | department | salary | |
|---|---|---|---|---|
| 1 | Aarav | [email protected] | Engineering | 95000 |
| 2 | Sara | [email protected] | Design | 78000 |
No primary key
A table without a primary key has no way to uniquely identify a specific row. You cannot reliably UPDATE or DELETE "just this row" — you can only match on content, which is ambiguous if two rows have identical values.
Add id INTEGER PRIMARY KEY — every production table should have a primary key.
Storing numbers in TEXT columns
Storing numeric values as TEXT breaks sorting ("9" sorts after "10" alphabetically) and arithmetic. Similarly, dates stored as TEXT work in SQLite with ISO format but are fragile in other databases.
Use REAL for money, INTEGER for counts, TEXT only for actual text. In Postgres, use NUMERIC(10,2) for currency to avoid floating-point rounding.
Key Concepts
Pro Tip
Constraints are the database's way of refusing bad data. A well-constrained schema prevents entire classes of bugs that application code would otherwise have to catch.
When to Use
Every new application, feature, or analysis pipeline starts with a table definition. Good constraints now prevent data-quality problems forever.