Skip to content

SQL Fundamentals

Database diagram

Every example on this page queries the same toy schema. Read it once, refer back as needed.

erDiagram
  USERS ||--o{ ORDERS : places

  USERS {
    int id PK
    string name
    string country
    string email
    string phone
    decimal lifetime_value
    timestamp created_at
  }
  ORDERS {
    int id PK
    int user_id FK
    string status
    decimal total
    timestamp created_at
  }
  LEADS {
    int id PK
    string email
    timestamp created_at
  }
  EMPLOYEES {
    int id PK
    string name
    int manager_id FK
  }

How to read this diagram

This is an ER (entity-relationship) diagram in Mermaid syntax. Conventions:

  • Each box is a table. Bold name on top; columns listed below as type name [PK|FK].
  • PK = primary key. Uniquely identifies a row — no two rows share the same PK value.
  • FK = foreign key. Column whose value is the PK of another table — the "join key" that says which row this one belongs to.
  • Line between two tables = a relationship. The endpoint markers are crow's-foot notation:

    • || exactly one
    • o| zero or one
    • }o zero or many
    • }| one or many

    So USERS ||--o{ ORDERS : places reads "one user places zero-or-many orders." - Self-loop (EMPLOYEES }o--o| EMPLOYEES : reports_to) is a relationship into the same table — many employees report to zero-or-one manager (the CEO has none). Standard pattern for trees/hierarchies (employees ↔ manager, comments ↔ parent, categories ↔ subcategory). - No line ≠ unrelated. USERS.email and LEADS.email aren't formally linked, but you can still match them as a set operation.

You'll see this schema referenced throughout the rest of the page.

Order of operations

SQL has two orders — the order you write a query and the order the engine executes it. Mixing them up is the #1 source of "column not found" and "can't use alias" errors.

Execution pipeline

This is how the database actually processes your query, step by step:

flowchart LR
  A["1 · FROM / JOIN"] --> B["2 · WHERE"]
  B --> C["3 · GROUP BY"]
  C --> D["4 · HAVING"]
  D --> E["5 · SELECT"]
  E --> F["6 · DISTINCT"]
  F --> G["7 · ORDER BY"]
  G --> H["8 · LIMIT / OFFSET"]

Writing vs execution

Click a clause to see when you write it vs when SQL runs it — and the practical gotcha that comes from the gap.

✏️ Writing order
⚙️ Execution order

Mnemonic — Funny Wizards Grill Hot Sausages During Open Lunch

FROM → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT

JOIN

JOINs let you combine rows from two tables on a related column. In our schema, the canonical example is usersorders ON users.id = orders.user_id.

Two-set Venn diagram for SQL joins Highlighted region updates when a JOIN type is selected. A B B1 B2 B3 A1 A2 A3 × × × × × × × × × SAME TABLE · TWO ALIASES t1 t2 = one table
INNER JOIN BEGINNER
A ∩ B · matches in both
Returns only rows whose key exists in both tables.
SELECT *
FROM   a
INNER JOIN b ON a.id = b.a_id;

SELECT basics

Pick rows or columns from a table. Click each variant to see what changes.

Input · users
Result

WHERE filters

Filter rows by condition. Highlighted rows match the predicate.

Input · users (highlighted = match)

Aggregation

Roll many rows up into summary numbers with GROUP BY.

Input · orders
Result

CASE expressions

Conditional logic inline — bucket values into categories.

Input · orders
Result · adds a segment column

Subqueries and CTEs

Same result, three readability shapes. Find orders from US users.

users
orders
Result · orders from US users

Window functions

Calculate over partitions without collapsing rows. Output gets one extra column.

Input · orders
Result · same rows, new column

Set operations

Combine the rows of two queries — like Venn diagrams over result sets. Both queries must have the same number and type of columns.

users.email
leads.email
Result

Date functions (Postgres)

Manipulate timestamps and intervals. Reference row: created_at = '2025-03-15 14:30:00', "today" = 2026-05-10.

Input · users
Result