SQL for Data Analysis

Joining Tables with INNER JOIN

Lesson 5 of 13

Data is split across tables to avoid repeating it. A customer’s name is stored once in customers, not on every order. A join puts the pieces back together.

The idea

orders.customer_id holds the id of the customer who placed the order. A join matches each order with the customer row that has the same id.

SELECT orders.order_id,
       orders.order_date,
       customers.name
FROM orders
INNER JOIN customers
        ON orders.customer_id = customers.customer_id;

ON states how the rows match. INNER JOIN returns only the rows that have a match in both tables. The word INNER is optional; JOIN alone means the same.

Table aliases

Short aliases make joins much easier to read.

SELECT o.order_id,
       o.order_date,
       c.name,
       c.city
FROM orders AS o
JOIN customers AS c ON o.customer_id = c.customer_id
WHERE o.status = 'delivered'
ORDER BY o.order_date;

When a column name exists in both tables, you must say which one you mean, as in o.customer_id. Qualifying every column is a good habit.

Joining more than two tables

Add one JOIN for each extra table. To see what was on each order, go from order_items to orders and to products:

SELECT o.order_id,
       p.name AS product,
       oi.quantity,
       p.price,
       oi.quantity * p.price AS line_total
FROM order_items AS oi
JOIN orders   AS o ON oi.order_id = o.order_id
JOIN products AS p ON oi.product_id = p.product_id
ORDER BY o.order_id;

Joins and aggregates together

This is the pattern behind most reports: join the tables, then group.

SELECT p.category,
       SUM(oi.quantity)           AS units,
       SUM(oi.quantity * p.price) AS revenue
FROM order_items AS oi
JOIN products AS p ON oi.product_id = p.product_id
GROUP BY p.category
ORDER BY revenue DESC;

Revenue per customer needs all four tables:

SELECT c.name,
       SUM(oi.quantity * p.price) AS revenue
FROM customers AS c
JOIN orders      AS o  ON o.customer_id = c.customer_id
JOIN order_items AS oi ON oi.order_id = o.order_id
JOIN products    AS p  ON p.product_id = oi.product_id
WHERE o.status <> 'cancelled'
GROUP BY c.name
ORDER BY revenue DESC;

Watch the row count

A join can multiply rows. Joining orders to order_items returns one row per item, not one per order, so COUNT(*) then counts items. To count orders after such a join, use COUNT(DISTINCT o.order_id).

Whenever a total looks too large, check whether a join has repeated the rows you are adding up.

Practice

List each delivered order with the customer’s name and the order’s total value.