Tables, rows and the relational model

SQL · Essentials · 8 min

Every SQL statement you will ever write operates on tables, so the mental model comes before the syntax.

A relational database keeps data in tables (formally, relations). Each table describes one kind of thing and has a fixed set of named, typed columns; each row is one instance of that thing. Throughout these lessons we use one small shop:

customers(id, name, email, city)
products(id, name, category, price)
orders(id, customer_id, status, total, ordered_at)
order_items(order_id, product_id, quantity, unit_price)

Tables do not point at each other. They share values instead: orders.customer_id holds the id of a customer, and a query matches the two up when you need both. Because relationships are just matching values, you can combine any tables on any columns at query time, including combinations nobody planned for when the schema was designed.

SQL is declarative. You describe the result you want ("London customers, sorted by name") and the database's planner decides how to produce it: which index to use, which table to read first. You never write the loops yourself.

A few facts to internalise early:

  • Rows have no inherent order. If you need an order, you must ask for one.
  • Every column has a type, and every value must fit it or be NULL (unknown).
  • Most tables have a primary key: a column whose value identifies each row uniquely, like customers.id.

SQL is an ISO standard, but every product speaks its own dialect. These lessons use PostgreSQL as the reference and point out where MySQL, SQL Server, SQLite or Oracle behave differently.

Example

-- The customers table: 4 rows, 4 columns
SELECT * FROM customers;
--  id | name  | email             | city
--   1 | Ana   | ana@example.com   | Yerevan
--   2 | Ben   | ben@example.com   | London
--   3 | Chloe | chloe@example.com | London
--   4 | Dev   | dev@example.com   | NULL

-- orders.customer_id holds a customers.id value
SELECT id, customer_id, status, total, ordered_at FROM orders;
--  id  | customer_id | status    | total  | ordered_at
--  101 |           1 | shipped   |  68.50 | 2026-01-05
--  102 |           1 | pending   | 189.00 | 2026-02-10
--  103 |           2 | shipped   |   9.99 | 2026-02-11
--  104 |           3 | cancelled |  49.00 | 2026-03-01
--  105 |           2 | shipped   | 238.00 | 2026-03-15

-- products: 1 Keyboard 49.00, 2 Mouse 19.50, 3 Monitor 189.00 (hardware),
--           4 Ebook 9.99 (digital)

Remember

  • A table is a set of rows; every row has the same typed columns
  • Relationships are expressed by storing matching key values, not pointers
  • SQL is declarative: say what you want, the planner decides how

Watch out for

  • Assuming rows come back in insertion order. Without ORDER BY the order is unspecified and can change between runs.

Check yourself

  1. How does a row in orders record which customer placed it?
  2. What does it mean that SQL is declarative?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.postgresql.org/docs/current/tutorial-concepts.html

Builds on

Nothing: this is a foundation.

Unlocks