CREATE TABLE Defines a Schema
Every piece of structured data in SQLite lives in a table, and every table starts with a CREATE TABLE statement. You give it a name, list the columns, and optionally attach constraints. SQLite writes the schema into the database file and the table is ready to use.
The smallest useful example:
Three columns, one primary key, one NOT NULL rule. SQLite filled in id automatically because it's an integer primary key, and let email be NULL for the second row because nothing said otherwise. That's the whole shape — name, columns, constraints — and everything else in this page is a variation on it.
The Syntax, Piece by Piece
A column definition is name TYPE constraint constraint .... The type is optional in classic SQLite (more on that on the type affinity page), but it's good practice to always include one — readers and tools rely on it.
A few things worth pointing out:
- Constraints chain together with spaces:
NOT NULL UNIQUEonskumeans both rules apply. DEFAULT 1onin_stocklets theINSERTskip that column.- SQLite uses
INTEGERfor booleans — there's no nativeBOOLEANtype.0is false,1is true. - Trailing comma after the last column is a syntax error. SQL is stricter than JavaScript here.
IF NOT EXISTS: Don't Crash on Re-Runs
Run a CREATE TABLE against a database that already has that table, and SQLite raises an error:
Error: table users already exists
That's fine the first time, annoying the hundredth. IF NOT EXISTS makes the statement a no-op when the table is already there:
The second CREATE TABLE does nothing — no error, no schema change. This is the form you want in startup code, migration scripts, and anywhere the same SQL might run more than once.
A warning, though: IF NOT EXISTS only checks the name. If a table with that name exists but has different columns, SQLite leaves it alone. It will not "fix" or "upgrade" the schema for you. That's what migrations are for.
Constraints: Rules That Travel With the Schema
Constraints are how you push validation into the database itself. The four you'll reach for constantly:
PRIMARY KEY— uniquely identifies a row. More on this in the primary keys doc.NOT NULL— the column must have a value.DEFAULT value— used when anINSERTomits the column. Can be a literal or an expression likedatetime('now').CHECK (expr)— must evaluate true for every row.UNIQUE (col, col)— table-level constraint enforcing uniqueness across the combination.
Constraints run on every INSERT and UPDATE. A row that violates one is rejected and the statement fails. Catching bad data in the database is much cheaper than catching it after it's spread through the application.
Foreign Keys
A foreign key says "this column points at a row in another table." It keeps your data consistent — you can't reference a user that doesn't exist, and (with the right options) deleting a user can cascade to their orders.
A SQLite gotcha worth memorising: foreign key enforcement is off by default. You have to run PRAGMA foreign_keys = ON for every connection that wants the constraints checked. Most application drivers either do this for you or expose a setting; if yours doesn't, run the pragma right after connecting.
ON DELETE CASCADE here means deleting a user automatically deletes their posts. Other options include SET NULL, RESTRICT, and the default — NO ACTION — which rejects the delete if children exist.
CREATE TABLE AS SELECT
Sometimes you want a quick copy of a query's result as a new table — for a snapshot, a backup, or a scratch table during analysis. CREATE TABLE ... AS SELECT does that:
The new table copies the column names, types (best-effort), and the data. What it doesn't copy is just as important: no primary key, no NOT NULL, no indexes, no foreign keys. It's a flat snapshot. Treat it as a starting point for ad-hoc work, not a way to clone a real schema.
If you want the structure without the data, add WHERE 0:
You get an empty table with the same column shape — handy for archive tables you'll fill later.
Temporary Tables
A TEMP table lives only for the current database connection. Close the connection and it's gone — no cleanup, no leftover schema:
Good uses: staging rows for a multi-step query, holding intermediate results too messy for a CTE, isolating per-connection data in a long-running session. CREATE TEMP TABLE and CREATE TEMPORARY TABLE mean the same thing.
You can combine it with AS SELECT too: CREATE TEMP TABLE snapshot AS SELECT ... is a common pattern for freezing a result set partway through analysis.
Quoting Names
Most of the time, column and table names are bare identifiers. If you have to use a reserved word or a name with spaces, wrap it in double quotes (the SQL standard) or backticks (a MySQL-ism that SQLite also accepts):
It works, but it's friction every time you reference the table. Prefer plain names like orders, selection, user_id and skip the quoting entirely.
A Realistic Example
Pulling the pieces together — a small schema for a tasks app, with IF NOT EXISTS so it can run on every startup:
That's a schema you can ship: idempotent creation, foreign keys enforced, a CHECK keeping done honest, sensible defaults, and timestamps that fill themselves in.
Next: Data Types
CREATE TABLE lets you write INTEGER, TEXT, REAL — but SQLite is famously relaxed about how it stores those values. The next page covers the five storage classes SQLite actually uses, and why the type you wrote isn't always the type you get.
Frequently Asked Questions
How do I create a table in SQLite?
Use CREATE TABLE name (column1 TYPE, column2 TYPE, ...). Each column gets a name and an optional type, and you can add constraints like PRIMARY KEY, NOT NULL, or DEFAULT. The statement runs immediately and the table sticks around in the database file.
What does IF NOT EXISTS do in CREATE TABLE?
CREATE TABLE IF NOT EXISTS name (...) only creates the table if a table with that name doesn't already exist. Without it, re-running the script against an existing database raises a table already exists error. It's the standard guard for migration scripts and app startup code.
Can I create a table from a SELECT in SQLite?
Yes — CREATE TABLE new_name AS SELECT ... builds a new table from a query's result set. The new table copies column names and data, but it does not copy constraints, primary keys, or indexes from the source. Use it for snapshots and scratch tables, not as a substitute for a real schema.
What's the difference between a temporary table and a regular table?
CREATE TEMP TABLE (or CREATE TEMPORARY TABLE) creates a table that exists only for the current database connection and disappears when the connection closes. Regular tables are persisted in the database file. Temp tables are useful for staging intermediate query results without polluting the schema.