Creating tables
Someone had to make the tables you have been querying. CREATE TABLE defines a new table: its name, its columns, the kind of data each column holds and the rules the data must obey. Good table design prevents bad data from getting in, which is far easier than cleaning it up afterwards.
A first table
CREATE TABLE suppliers (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
city TEXT,
phone TEXT
);Result
Done.
Each line inside the brackets defines one column: its name, its type, and any rules. Commas separate the columns. A new table is empty.
INSERT INTO suppliers (name, city, phone)
VALUES ('Punjab Grain Traders', 'Ludhiana', '98140 00000');Result
Done. 1 row changed.
Data types
A type says what kind of value a column may hold. These are the common ones.
| Kind of data | Usual type names |
|---|---|
| Whole numbers | INTEGER, INT, BIGINT, SMALLINT |
| Exact decimals, such as money | DECIMAL(10,2), NUMERIC(10,2) |
| Approximate decimals | REAL, FLOAT, DOUBLE |
| Short text | VARCHAR(100) |
| Long text | TEXT |
| Dates and times | DATE, TIME, TIMESTAMP or DATETIME |
| True or false | BOOLEAN |
VARCHAR(100) means text of up to 100 characters. DECIMAL(10,2) means a number with up to 10 digits in all, 2 of them after the decimal point.
Some advice on choosing:
- Money goes in
DECIMAL, never inFLOAT. Floating-point numbers cannot store values such as 0.10 exactly, and the tiny errors add up. Our practice tables keep prices as whole rupees for simplicity. - Phone numbers, PIN codes and account numbers are text, not numbers. You never add them up, they can start with zero, and they may contain spaces or a plus sign.
- Dates go in a date type where the database has one, not in text.
SQLite is unusually relaxed about types. It has only a few storage kinds (integer, real, text, blob), has no separate date type, and by default will store text in a column declared as a number. That is why our practice tables keep dates as text. MySQL, PostgreSQL, SQL Server and Oracle are strict: a column declared INTEGER holds only whole numbers.
Constraints: rules on the data
A constraint is a rule that every row must satisfy. The database refuses any insert or update that would break one.
CREATE TABLE coupons (
id INTEGER PRIMARY KEY,
code TEXT NOT NULL UNIQUE,
percent_off INTEGER NOT NULL CHECK (percent_off BETWEEN 1 AND 90),
active INTEGER NOT NULL DEFAULT 1,
created TEXT NOT NULL DEFAULT '2026-11-12'
);Result
Done.
| Constraint | Rule |
|---|---|
PRIMARY KEY | identifies the row: unique and never NULL |
NOT NULL | a value is required |
UNIQUE | no two rows may have the same value |
DEFAULT value | used when an insert leaves the column out |
CHECK (condition) | the condition must hold for the row |
FOREIGN KEY | the value must exist in another table |
See them at work. A plain insert uses the defaults:
The same code again breaks UNIQUE:
INSERT INTO coupons (code, percent_off) VALUES ('DIWALI10', 20);What the database says
Error: UNIQUE constraint failed: coupons.codeA discount of 150 percent breaks the CHECK:
INSERT INTO coupons (code, percent_off) VALUES ('MISTAKE', 150);What the database says
Error: CHECK constraint failed: percent_off BETWEEN 1 AND 90A missing code breaks NOT NULL:
INSERT INTO coupons (percent_off) VALUES (5);What the database says
Error: NOT NULL constraint failed: coupons.codeEach refusal is a bug caught before it reached the data.
The primary key
Every table should have one. It is the column, or occasionally the pair of columns, that identifies each row. The usual choice is an id number that the database hands out, as covered in the lesson on INSERT. Avoid using something from the real world, such as a phone number or an email address, as the primary key. Those change, and a primary key should never have to.
Foreign keys: linking tables
A foreign key says "the value in this column must be the primary key of a row in that table". It is what keeps an order from pointing to a customer who does not exist.
CREATE TABLE reviews (
id INTEGER PRIMARY KEY,
product_id INTEGER NOT NULL,
customer_id INTEGER NOT NULL,
stars INTEGER NOT NULL CHECK (stars BETWEEN 1 AND 5),
comment TEXT,
FOREIGN KEY (product_id) REFERENCES products(id),
FOREIGN KEY (customer_id) REFERENCES customers(id)
);Result
Done.
INSERT INTO reviews (product_id, customer_id, stars, comment)
VALUES (13, 1, 5, 'Keeps water cold all day');Result
Done. 1 row changed.
SELECT p.name, c.name AS customer, r.stars, r.comment
FROM reviews r
JOIN products p ON r.product_id = p.id
JOIN customers c ON r.customer_id = c.id;Result
| name | customer | stars | comment |
|---|---|---|---|
| Steel Bottle 1 L | Asha Verma | 5 | Keeps water cold all day |
1 row
The foreign keys are exactly the columns used in the ON of the joins. Table design and querying are two sides of the same idea.
As the last lesson said, SQLite checks foreign keys only when a program switches the check on, so in the practice box a review for product 999 is accepted. The other major databases would refuse it.
A key made of two columns
In a junction table, the natural key is the pair of ids. Written this way, the same product cannot be put into the same order twice.
CREATE TABLE wishlist (
customer_id INTEGER NOT NULL REFERENCES customers(id),
product_id INTEGER NOT NULL REFERENCES products(id),
added TEXT NOT NULL,
PRIMARY KEY (customer_id, product_id)
);Result
Done.
If the table already exists
Creating a table whose name is taken is an error.
CREATE TABLE suppliers (id INTEGER);What the database says
Error: table suppliers already existsCREATE TABLE IF NOT EXISTS makes the table only if it is not there, and otherwise does nothing. Scripts that may run more than once use it. SQLite, MySQL and PostgreSQL support it.
A table from a query
CREATE TABLE ... AS SELECT makes a new table from the result of a query and fills it in one step. It is handy for snapshots and for trying things on a copy.
CREATE TABLE home_products AS
SELECT id, name, price FROM products WHERE category = 'Home';Result
Done.
SELECT * FROM home_products;Result
| id | name | price |
|---|---|---|
| 13 | Steel Bottle 1 L | 450 |
| 14 | Pressure Cooker 3 L | 1450 |
| 15 | Bedsheet Double | 899 |
| 16 | Wall Clock | 349 |
4 rows
The copy gets the columns and the data but not the constraints. Its id is no longer a primary key. SQL Server writes this as SELECT ... INTO new_table FROM ....
Seeing what tables exist
Each database has its own way to list tables. In SQLite, a built-in table called sqlite_master holds the list.
SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name;Result
| name |
|---|
| coupons |
| customers |
| employees |
| home_products |
| order_items |
| orders |
| products |
| reviews |
| suppliers |
| wishlist |
10 rows
MySQL uses SHOW TABLES, and MySQL, PostgreSQL and SQL Server share a standard set of views called information_schema.
Design advice in brief
- One table for each kind of thing: customers, products, orders. Do not mix them.
- Store each fact once. If the same city name has to be typed into many rows of many tables, the design can be improved.
- Give every table a primary key.
- Link tables with foreign keys.
- Do not store what can be worked out. An order's total is the sum of its lines.
- Say
NOT NULLwherever a value must exist. AllowNULLonly where "unknown" is a real possibility. - Use clear, consistent names in small letters with underscores:
order_date, notOrdDt.
The formal name for the first three points is normalisation, and the interview questions page says more about it.
Try it yourself
Exercise 1. Create a table categories with an integer primary key id and a required, unique text column name.
Show one answer
CREATE TABLE categories (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE);Done.
Exercise 2. Create a table payments with: id (integer primary key), order_id (required integer that refers to orders), amount (required integer that must be more than 0) and paid_on (required text).
Show one answer
CREATE TABLE payments (id INTEGER PRIMARY KEY, order_id INTEGER NOT NULL REFERENCES orders(id), amount INTEGER NOT NULL CHECK (amount > 0), paid_on TEXT NOT NULL);Done.
Exercise 3. Create a table cheap_products from a query: the name and price of every product that costs less than 150.
Show one answer
CREATE TABLE cheap_products AS SELECT name, price FROM products WHERE price < 150;Done.
Examples on this page were run with SQLite 3.50.4 on 12 November 2026. The results shown are the real results.
This is an independent learning site. It is not connected with the makers of any database product. MySQL, PostgreSQL, SQL Server, Oracle and SQLite are named only so that you know where a piece of SQL works. The shop, its customers and its staff in the practice database are made up.