Skip to content
/ Chapter 5 · Joins
Lesson 5.3 · garden_shop
Lesson 5.3

INNER JOIN vs LEFT JOIN in SQL

INNER JOIN keeps matches only. LEFT JOIN keeps every row from the left table.

An INNER JOIN returns only rows with a match on both sides. That is often what you want, but it hides rows that have no related record.

A LEFT JOIN keeps every row from the left table. Missing matches from the right table appear as NULL.

Pattern
select left_table.id, count(right_table.id) as match_count
from left_table
left join right_table
  on left_table.id = right_table.left_id
group by left_table.id
Ready to run it?

Open the DuckDB playground with the matching dataset and query already filled in.

See the difference on Garden Shop data

Some customers have orders and some don't, which is what makes this a useful place to watch the two joins behave differently. An INNER JOIN between customers and orders drops customers with no orders, because there is no matching order row to return.

INNER JOIN: customers with orders only
select c.customer_id,
       c.first_name,
       o.order_id
from customers as c
inner join orders as o
  on o.customer_id = c.customer_id
order by c.customer_id, o.order_id

A LEFT JOIN starts from customers and keeps every customer. If a customer has no matching order, the order columns are NULL.

LEFT JOIN: every customer stays
select c.customer_id,
       c.first_name,
       o.order_id
from customers as c
left join orders as o
  on o.customer_id = c.customer_id
order by c.customer_id, o.order_id

Count the right column

This is the part the exercise turns on. After a left join, count(*) counts the kept left row even when the right side is missing; count(o.order_id) counts only matched orders, which is why no-order customers show 0.

Common JOIN gotchas

Schema · Garden Shop Tables · customers, orders 2 tables
Table · customers

One row per customer. Some customers have no phone on file.

9 columns · 20 rows
customer_id int first_name text last_name text email text phone text city text state text signup_date date is_active bool
Table · orders

One row per order. Unshipped orders have a null shipped_date.

6 columns · 24 rows
order_id int customer_id int order_date date shipped_date date status text coupon_code text
Your task

Return every customer with their total order count. Include customers who have placed no orders. Sort by order count so customers with no orders appear first, then by customer id.

·

Run your query to inspect the result.

Expected answer
  • Columns: customer_id, first_name, last_name, order_count.
  • Rows: 20 customers.
  • Four customers should have order_count 0: Liam, Emma, Chloe, and Daniel.