Introduction to PostgreSQL INNER JOIN
In a relational database, data is typically distributed across multiple related tables. INNER JOIN is the most common way to retrieve data from multiple tables based on a matching condition.
Syntax:
SELECT select_list
FROM table1
INNER JOIN table2 ON table1.column = table2.column;
The INNER keyword is optional — JOIN alone means INNER JOIN.
How INNER JOIN works
For each row in table1, PostgreSQL compares the join column with every row in table2. When the values match, it creates a result row combining columns from both tables. Rows with no match in either table are excluded.
Example: joining two tables
Using the classic DVD rental schema — every payment belongs to one customer, identified by customer_id:
SELECT
customer.customer_id,
customer.first_name,
customer.last_name,
payment.amount,
payment.payment_date
FROM customer
INNER JOIN payment ON payment.customer_id = customer.customer_id
ORDER BY payment.payment_date;
This returns only customers who have at least one payment (rows with no payments are excluded).
Using table aliases
Table aliases make multi-table queries more readable:
SELECT c.customer_id, c.first_name, p.amount, p.payment_date
FROM customer c
INNER JOIN payment p ON p.customer_id = c.customer_id
ORDER BY p.payment_date DESC
LIMIT 20;
The USING shorthand
When both tables share the same column name for the join key, use USING:
SELECT c.first_name, p.amount
FROM customer c
INNER JOIN payment p USING (customer_id);
PostgreSQL outputs customer_id only once (not duplicated from both tables).
Joining three tables
SELECT
c.first_name || ' ' || c.last_name AS customer,
f.title AS film,
p.amount
FROM customer c
INNER JOIN payment p ON p.customer_id = c.customer_id
INNER JOIN rental r ON r.rental_id = p.rental_id
INNER JOIN inventory i ON i.inventory_id = r.inventory_id
INNER JOIN film f ON f.film_id = i.film_id
ORDER BY c.last_name, f.title;
Production tips
- Index all foreign key columns used in JOIN conditions — unindexed joins on large tables trigger sequential scans.
- Avoid
SELECT *in joins — column name ambiguity and unnecessary data transfer are common issues. - Use
EXPLAIN ANALYZEto verify the join strategy (hash join, nested loop, merge join). - For very selective filters, put the most selective condition in the WHERE clause so the planner can use it to limit rows before the join.
Reference: PostgreSQL documentation — JOIN types.