Skip to content

JOINs ​

A JOIN is how you rebuild the nested JSON your UI expects - without storing nested blobs.

Assume:

sql
CREATE TABLE companies (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE contacts (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL,
  email TEXT NOT NULL,
  company_id INTEGER,
  FOREIGN KEY (company_id) REFERENCES companies(id)
);

INSERT INTO companies (id, name) VALUES
  (1, 'Analytical Engines'),
  (2, 'Navy Codeworks');

INSERT INTO contacts (name, email, company_id) VALUES
  ('Ada Lovelace', 'ada@example.com', 1),
  ('Grace Hopper', 'grace@example.com', 2),
  ('Lone Wolf', 'wolf@example.com', NULL);

INNER JOIN ​

Only rows that match on both sides:

sql
SELECT
  c.name AS contact_name,
  co.name AS company_name
FROM contacts AS c
INNER JOIN companies AS co ON co.id = c.company_id;

Lone Wolf disappears - no company match.

FE: contacts.filter(c => c.companyId != null).map(… include company).

LEFT JOIN ​

Keep all rows from the left table; fill right side with NULL when missing:

sql
SELECT
  c.name AS contact_name,
  co.name AS company_name
FROM contacts AS c
LEFT JOIN companies AS co ON co.id = c.company_id;

Now Lone Wolf shows up with company_name = NULL. This is what most "list contacts with company" screens want.

JOIN ≈ include / expand ​

API vibeSQL vibe
GET /contacts?include=companyLEFT JOIN companies
only contacts that have a companyINNER JOIN
company with its contactsjoin the other direction / separate query

Multiple joins ​

sql
SELECT
  d.title AS deal,
  c.name AS contact,
  co.name AS company
FROM deals AS d
LEFT JOIN contacts AS c ON c.id = d.contact_id
LEFT JOIN companies AS co ON co.id = c.company_id;

Chain of includes. Watch for fan-out: joining one-to-many twice can multiply rows (one deal × many tags × many activities = surprise row count). Aggregate or query separately when that happens.

Self-join (quick mention) ​

A table joining itself - e.g. employees.manager_id → employees.id. Same JOIN syntax, two aliases:

sql
SELECT e.name AS employee, m.name AS manager
FROM employees AS e
LEFT JOIN employees AS m ON m.id = e.manager_id;

Selecting the shape ​

You still get a flat result set from SQL. Nesting into { company: { … } } is the job of your API / ORM / serializer. SQL's job is to get the right columns onto the same row.

As you can see - JOINs aren't scary. They're just "bring the related columns along".

Next: why some of these get slow - Indexes.

Notes-style SQL tutorial for front-end folks