ShareYourGroupShareYourGroupSQL

Lesson 21 of 26 · see the practice database

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.

SELECT * FROM suppliers;

Result

idnamecityphone
1Punjab Grain TradersLudhiana98140 00000

1 row

Data types

A type says what kind of value a column may hold. These are the common ones.

Kind of dataUsual type names
Whole numbersINTEGER, INT, BIGINT, SMALLINT
Exact decimals, such as moneyDECIMAL(10,2), NUMERIC(10,2)
Approximate decimalsREAL, FLOAT, DOUBLE
Short textVARCHAR(100)
Long textTEXT
Dates and timesDATE, TIME, TIMESTAMP or DATETIME
True or falseBOOLEAN

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:

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.

ConstraintRule
PRIMARY KEYidentifies the row: unique and never NULL
NOT NULLa value is required
UNIQUEno two rows may have the same value
DEFAULT valueused when an insert leaves the column out
CHECK (condition)the condition must hold for the row
FOREIGN KEYthe value must exist in another table

See them at work. A plain insert uses the defaults:

INSERT INTO coupons (code, percent_off) VALUES ('DIWALI10', 10);

Result

Done. 1 row changed.

SELECT * FROM coupons;

Result

idcodepercent_offactivecreated
1DIWALI101012026-11-12

1 row

The same code again breaks UNIQUE:

INSERT INTO coupons (code, percent_off) VALUES ('DIWALI10', 20);

What the database says

Error: UNIQUE constraint failed: coupons.code

A 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 90

A missing code breaks NOT NULL:

INSERT INTO coupons (percent_off) VALUES (5);

What the database says

Error: NOT NULL constraint failed: coupons.code

Each 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

namecustomerstarscomment
Steel Bottle 1 LAsha Verma5Keeps 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 exists

CREATE 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.

CREATE TABLE IF NOT EXISTS suppliers (id INTEGER);

Result

Done.

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

idnameprice
13Steel Bottle 1 L450
14Pressure Cooker 3 L1450
15Bedsheet Double899
16Wall Clock349

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

  1. One table for each kind of thing: customers, products, orders. Do not mix them.
  2. Store each fact once. If the same city name has to be typed into many rows of many tables, the design can be improved.
  3. Give every table a primary key.
  4. Link tables with foreign keys.
  5. Do not store what can be worked out. An order's total is the sum of its lines.
  6. Say NOT NULL wherever a value must exist. Allow NULL only where "unknown" is a real possibility.
  7. Use clear, consistent names in small letters with underscores: order_date, not OrdDt.

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.

Open in the practice box

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).

Open in the practice box

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.

Open in the practice box

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.