CREATE TABLE & Data Types
Reviewed & published by Brayan K
So far you've queried tables someone else built. Now you become the architect. CREATE TABLE defines a new table's columns and the data type each one holds — the foundation every other SQL command stands on.
Part of the free SQL course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end you'll know the everyday data types, how to pick the right one (especially for money), and how to add NOT NULL, DEFAULT, and a PRIMARY KEY as you create a table.
What You'll Learn
- Write a CREATE TABLE statement
- Choose between INTEGER, VARCHAR, TEXT, DECIMAL, BOOLEAN, DATE
- Why DECIMAL — not FLOAT — for money
- Add NOT NULL and DEFAULT column options
- Declare a PRIMARY KEY and auto-increment ids
- Use IF NOT EXISTS and CREATE TABLE AS SELECT
The Most Common Data Types
A column's type decides what it can store and how much space it takes. Keep this cheat sheet handy.
1. Your First CREATE TABLE
The pattern is CREATE TABLE name (column type, column type, …). Each line names a column and the type of data it will hold. The table starts empty — you add rows later with INSERT.
Creating a table is like printing a blank spreadsheet with labelled, typed columns: "this column only accepts dates, this one only whole numbers". The rules are set before any data arrives.
-- The simplest CREATE TABLE: a name, then columns with types.
CREATE TABLE customers (
id INTEGER,
full_name VARCHAR(100),
email VARCHAR(255),
signup_date DATE
);
-- CREATE TABLE prints nothing — it just makes an empty table. To see that it
-- worked, put a row in and read it back.
INSERT INTO customers VALUES (1, 'Ada Lovelace', 'ada@example.com', '2026-03-14');
SELECT * FROM customers;
-- ✅ Expected output:
-- id | full_name | email | signup_date
-- 1 | Ada Lovelace | ada@example.com | 2026-03-142. A Tour of Data Types
Choosing the right type matters for correctness and storage. The golden rule: use DECIMAL for money. REAL/FLOAT are approximate and introduce rounding errors that are unacceptable for currency.
-- A tour of the most common data types.
CREATE TABLE products (
id INTEGER, -- whole numbers
sku CHAR(8), -- fixed-length text, always 8 chars
name VARCHAR(120), -- variable text up to 120 chars
description TEXT, -- long, unbounded text
price DECIMAL(8,2), -- exact money: up to 999999.99
weight_kg REAL, -- approximate floating-point number
in_stock BOOLEAN, -- true / false
added_at TIMESTAMP -- date + time
);
INSERT INTO products VALUES
(1, 'KB-00291', 'Mechanical keyboard', 'Tactile switches, 87 keys',
129.99, 0.98, TRUE, '2026-03-14 09:30:00');
SELECT id, sku, name, price, weight_kg, in_stock FROM products;
-- Note what comes back for in_stock: 1, not "true". SQLite has no separate
-- boolean type and stores TRUE/FALSE as 1/0. Postgres and MySQL print true.
-- Use DECIMAL for money — never REAL/FLOAT (rounding errors!).
-- ✅ Expected output:
-- id | sku | name | price | weight_kg | in_stock
-- 1 | KB-00291 | Mechanical keyboard | 129.99 | 0.98 | 13. NOT NULL and DEFAULT
Two column options you'll use constantly: NOT NULL forces every row to supply a value, and DEFAULT fills in a value when you don't. Together they keep your data tidy from day one.
-- NOT NULL forces a value; DEFAULT supplies one when you omit the column.
CREATE TABLE accounts (
id INTEGER,
username VARCHAR(40) NOT NULL, -- must always be provided
is_active BOOLEAN DEFAULT TRUE, -- defaults to true
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
bio TEXT -- nullable (optional)
);
-- Insert only id and username; the other three columns fill themselves.
INSERT INTO accounts (id, username) VALUES (1, 'ava');
-- created_at is a reading of the clock, so it is different every time you run
-- this. Checking that it is NOT NULL proves the default fired without claiming
-- a timestamp that cannot be true twice.
SELECT id, username, is_active, bio, created_at IS NOT NULL AS has_timestamp
FROM accounts;
-- ✅ Expected output:
-- id | username | is_active | bio | has_timestamp
-- 1 | ava | 1 | NULL | 14. Adding a PRIMARY KEY
Every table should have a PRIMARY KEY — a column whose value uniquely identifies each row. It's automatically NOT NULL and unique. Auto-incrementing ids are the most common choice, though the keyword differs by engine.
-- PRIMARY KEY: a column that uniquely identifies each row.
CREATE TABLE users (
id INTEGER PRIMARY KEY, -- unique, not null, the row's identity
email VARCHAR(255) NOT NULL,
name VARCHAR(100)
);
INSERT INTO users VALUES (1, 'ada@example.com', 'Ada');
INSERT INTO users VALUES (2, 'alan@example.com', 'Alan');
SELECT * FROM users ORDER BY id;
-- Try adding a third row with id 1 and the insert is rejected: that is the
-- primary key doing its job.
--
-- Auto-numbering keywords differ by engine:
-- Postgres: id SERIAL PRIMARY KEY
-- MySQL: id INT AUTO_INCREMENT PRIMARY KEY
-- SQLite: id INTEGER PRIMARY KEY (auto-rowid)
-- ✅ Expected output:
-- id | email | name
-- 1 | ada@example.com | Ada
-- 2 | alan@example.com | AlanYour Turn: a tasks table
Fill in the blanks to create a small tasks table with a primary key, a required title, and a default flag.
-- 🎯 YOUR TURN — fill in the blanks
-- Goal: a "tasks" table with an integer id primary key, a required
-- title (max 200 chars), and a "done" flag defaulting to false.
CREATE TABLE tasks (
id INTEGER ____ KEY, -- 👉 PRIMARY
title VARCHAR(200) ____ , -- 👉 NOT NULL
done BOOLEAN DEFAULT ____ -- 👉 FALSE
);
-- ✅ Expected: an empty 3-column table; inserting a row without "done"
-- stores done = false.5. IF NOT EXISTS
Re-running a setup script shouldn't crash. CREATE TABLE IF NOT EXISTS creates the table only if it's missing, turning a duplicate run into a harmless no-op.
-- IF NOT EXISTS avoids an error if the table is already there.
CREATE TABLE IF NOT EXISTS settings (
key VARCHAR(50) PRIMARY KEY,
value TEXT
);
-- Run the very same statement again. Without IF NOT EXISTS this second one
-- would fail with "table settings already exists"; here it is a harmless
-- no-op, which is what makes setup scripts safe to re-run.
CREATE TABLE IF NOT EXISTS settings (
key VARCHAR(50) PRIMARY KEY,
value TEXT
);
INSERT INTO settings VALUES ('theme', 'dark');
SELECT * FROM settings;
-- ✅ Expected output:
-- key | value
-- theme | dark6. CREATE TABLE AS SELECT
You can build a table directly from a query's results — handy for snapshots and quick backups. The new table inherits the columns and data, though usually not the keys or constraints.
-- You can also build a table from a query (a quick snapshot/backup).
-- First, the table this lesson queries — a snippet should create what it reads.
CREATE TABLE products (
id INTEGER, product_name VARCHAR(120), category VARCHAR(40), price DECIMAL(8,2)
);
INSERT INTO products VALUES
(1, 'Mechanical keyboard', 'Electronics', 129.99),
(2, 'Standing desk', 'Furniture', 349.00),
(3, 'USB-C hub', 'Electronics', 59.50);
CREATE TABLE electronics AS
SELECT id, product_name, price
FROM products
WHERE category = 'Electronics';
SELECT * FROM electronics ORDER BY id;
-- The new table inherits the result's columns and data.
-- Note: this copies the DATA but usually NOT keys/constraints.
-- 59.5 rather than 59.50 is your engine printing the number, not losing it.
-- ✅ Expected output:
-- id | product_name | price
-- 1 | Mechanical keyboard | 129.99
-- 3 | USB-C hub | 59.5Reorder Challenge
These lines of a CREATE TABLE statement are scrambled. Put them in the correct order:
A. email VARCHAR(255) NOT NULL,
B. CREATE TABLE users (
C. );
D. id INTEGER PRIMARY KEY,
E. name VARCHAR(100)Quick Recall
1) Which type should store a product price like 19.99?
DECIMAL(p, s) — exact base-10. Never FLOAT/REAL, which round and would corrupt money totals.
2) You insert a row but omit a DEFAULT TRUE column. What is stored?
TRUE. DEFAULT supplies the value when you don't provide one.
3) What does PRIMARY KEY imply about a column besides uniqueness?
It's automatically NOT NULL — a primary key can never be empty, since it identifies the row.
Common Errors (and the fix)
- Trailing comma: a comma after the last column before ) is a syntax error. Remove it.
- "table already exists": use CREATE TABLE IF NOT EXISTS or drop it first.
- FLOAT for money: rounding errors creep in. Use DECIMAL.
- Missing length on VARCHAR (some engines): MySQL needs VARCHAR(n); pick a sensible n.
- Reserved words as names: avoid columns named order, group, etc., or quote them carefully.
Mini-Challenge: A Books Table
Design a real table with the right type for each column. Write the CREATE TABLE, then run it to confirm it's valid.
-- 🎯 MINI-CHALLENGE
-- Design a "books" table:
-- - id: integer, primary key
-- - title: text up to 200 chars, required
-- - price: exact money with 2 decimal places
-- - published: a date
-- - in_print: boolean, defaults to true
--
-- ✅ Expected: a valid CREATE TABLE statement that runs without error.
-- your CREATE TABLE here🎉 Lesson Complete
- ✅ CREATE TABLE name (col type, …) defines a new table
- ✅ Pick types deliberately — DECIMAL for money, never FLOAT
- ✅ NOT NULL requires values; DEFAULT supplies them
- ✅ A PRIMARY KEY is unique and automatically NOT NULL
- ✅ IF NOT EXISTS and CREATE TABLE AS SELECT are handy extras
- ✅ Next: ALTER TABLE — changing a table after it exists
Practice quiz
What is the basic shape of a CREATE TABLE statement?
- CREATE TABLE name SELECT ...
- CREATE name TABLE (columns)
- CREATE TABLE name (column type, column type, ...)
- TABLE CREATE name (columns)
Answer: CREATE TABLE name (column type, column type, ...). The pattern is CREATE TABLE name (column type, column type, ...), naming each column and its type.
Which data type should you use to store money like 19.99?
- DECIMAL
- FLOAT
- REAL
- INTEGER
Answer: DECIMAL. DECIMAL stores exact base-10 values; FLOAT/REAL are approximate and cause rounding errors for money.
What does the NOT NULL constraint do?
- Fills the column with zeros
- Makes the column unique
- Sets a default value
- Forbids the column from holding NULL — a value must always be provided
Answer: Forbids the column from holding NULL — a value must always be provided. NOT NULL requires every row to supply a value for that column.
What does DEFAULT TRUE do for a BOOLEAN column?
- Forces every row to be true
- Supplies true when the column is omitted on insert
- Makes the column required
- Deletes false rows
Answer: Supplies true when the column is omitted on insert. DEFAULT supplies the given value when you don't provide one on INSERT.
Besides being unique, what does PRIMARY KEY imply about a column?
- It is automatically NOT NULL
- It is automatically a foreign key
- It allows duplicate values
- It defaults to zero
Answer: It is automatically NOT NULL. A PRIMARY KEY uniquely identifies each row and is automatically NOT NULL.
What does VARCHAR(120) store?
- Exactly 120 characters always
- 120 rows
- Variable-length text up to 120 characters
- A number up to 120
Answer: Variable-length text up to 120 characters. VARCHAR(n) holds variable-length text up to n characters, storing only what you put in.
How does CHAR(8) differ from VARCHAR(8)?
- CHAR is for numbers
- CHAR is fixed-length and pads to 8 characters; VARCHAR is variable-length
- They are identical
- CHAR holds longer text
Answer: CHAR is fixed-length and pads to 8 characters; VARCHAR is variable-length. CHAR(n) is fixed-length and pads shorter values; VARCHAR(n) is variable up to the limit.
What does CREATE TABLE IF NOT EXISTS settings (...) do when the table already exists?
- Drops and recreates it
- Raises an error
- Empties the table
- Does nothing (a harmless no-op)
Answer: Does nothing (a harmless no-op). IF NOT EXISTS creates the table only when missing, turning a duplicate run into a no-op.
What does CREATE TABLE electronics AS SELECT ... do?
- Renames an existing table
- Builds a new table from a query's result columns and data
- Deletes rows from a table
- Adds a column
Answer: Builds a new table from a query's result columns and data. CREATE TABLE AS SELECT builds a new table from a query, copying its columns and data (usually not keys).
What common mistake is a comma after the last column before the closing parenthesis?
- It is required
- It creates an extra column
- It is a trailing-comma syntax error
- It defaults the column
Answer: It is a trailing-comma syntax error. A trailing comma after the final column before ) is a syntax error and must be removed.
Continue this course
- Previous: UNION, INTERSECT & EXCEPT
- Next: ALTER TABLE — Add, drop and modify columns on existing tables
- Quick reference: SQL cheat sheet › Data Types
- From the blog: SQL Database Design Best Practices
Frequently asked questions
What's the difference between CHAR, VARCHAR, and TEXT?
CHAR(n) is fixed-length and pads shorter values with spaces — good for codes that are always the same length (like a 2-letter country code). VARCHAR(n) is variable-length up to a limit and only stores what you put in. TEXT is for long, unbounded strings like articles or comments. Use VARCHAR for most short strings and TEXT for large free-form content.
Why use DECIMAL for money instead of FLOAT or REAL?
FLOAT and REAL are approximate binary floating-point types, so values like 0.1 can't be stored exactly and arithmetic accumulates tiny rounding errors. DECIMAL(p, s) stores exact base-10 values with a fixed number of decimal places, which is essential for currency. Always use DECIMAL (or NUMERIC) for prices and balances.
What does NOT NULL actually do?
NOT NULL is a constraint that forbids the column from holding NULL (the absence of a value). Any INSERT or UPDATE that would leave it empty is rejected. Use it for columns that must always have a value, like a username or an order total. Columns without NOT NULL are optional and may be NULL.
How do I make an id auto-increment?
It varies by engine: PostgreSQL uses SERIAL (or GENERATED ALWAYS AS IDENTITY), MySQL uses INT AUTO_INCREMENT, and SQLite auto-generates rowids for an INTEGER PRIMARY KEY. All three give each new row a unique, automatically growing id so you don't have to supply it yourself.