FULL JOIN
Objectives
By the end of this lesson, you should be able to:
- Keep every row from both tables with
FULL JOIN - Explain when
FULL JOINis the right choice overLEFT JOINorINNER JOIN
💡 Why this matters: Some questions need both directions of “unmatched” at once, “every category, even empty ones, and every product, even ones with no category set.” Neither
LEFT JOINnorRIGHT JOINalone covers both sides.
⚠️ A note on verification: every statement and result in this lesson was run against a real, live PostgreSQL 18 database.
Setup
CREATE TABLE categories (
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL
);
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
category_id INTEGER REFERENCES categories(id)
);
INSERT INTO categories (name) VALUES ('Electronics'), ('Home'), ('Office'), ('Outdoors');
INSERT INTO products (name, category_id) VALUES
('Wireless Mouse', 1),
('Desk Lamp', 2),
('Notebook', 3),
('Mystery Item', NULL);
Outdoors has no products in it, and Mystery Item has no category set (category_id is NULL, allowed since this foreign key column isn’t NOT NULL). Both kinds of “unmatched” exist in this data.
FULL JOIN
SELECT c.name AS category, p.name AS product
FROM categories c FULL JOIN products p ON c.id = p.category_id
ORDER BY category NULLS LAST, product NULLS LAST;
category | product
---------------+------------------
Electronics | Wireless Mouse
Home | Desk Lamp
Office | Notebook
Outdoors |
| Mystery Item
FULL JOIN (also written FULL OUTER JOIN, OUTER is optional and usually omitted) keeps every row from both tables. Outdoors appears with a NULL product, no matching row in products. Mystery Item appears with a NULL category, no matching row in categories. Neither LEFT JOIN nor RIGHT JOIN alone would show both of these gaps in the same result, LEFT JOIN categories to products would show Outdoors but not Mystery Item, and the reverse would show Mystery Item but not Outdoors.
When to Reach for FULL JOIN
FULL JOIN comes up less often than INNER JOIN or LEFT JOIN in everyday queries, but it’s exactly right for reconciliation-style questions: “show me every category and every product, and make any gaps on either side visible,” useful for spotting empty categories and uncategorized products in the same report.
Try It
- Write the
FULL JOINquery above and confirm bothOutdoorsandMystery Itemappear with aNULLon the other side. - Add a
WHERE p.id IS NULLclause to isolate only categories with no products. - Add a
WHERE c.id IS NULLclause (instead) to isolate only products with no category. - Explain, in your own words, the difference between what
LEFT JOIN,RIGHT JOIN, andFULL JOINeach guarantee about which rows survive.
Recap
FULL JOIN(orFULL OUTER JOIN) keeps every row from both tables, filling inNULLwherever no match exists on the other side.- It’s the only join type that surfaces unmatched rows from both directions in a single query.
- Combined with
IS NULLon either side’s key column, it isolates exactly the unmatched rows from that side.
Next lesson: CROSS JOIN, every combination of rows from both tables, with no matching condition at all.