LearnFrenzy
Sign in Take a free test

SQL Joins · Lesson 15 of 30

SQL Self Join, Cross Join and Multi-Table Joins

6 min read Updated Nov 7, 2026 Saurabh Samir

Two more join patterns complete your toolkit. A self join joins a table to itself – the classic "show each employee with their manager" interview question. A cross join pairs every row with every other row, useful for generating combinations. Finally, you'll put it all together in a four-table query of the kind real reports use.

In this lesson

  • Self joins for hierarchies and comparisons
  • CROSS JOIN for combinations
  • Joining four tables cleanly
  • Non-equi joins

Self join: employees and managers

In employees, manager_id points to another row in the same table. To show both names, use the table twice with two aliases:

SELECT e.name       AS employee,
       e.department,
       m.name       AS manager
FROM employees e
LEFT JOIN employees m ON m.emp_id = e.manager_id
ORDER BY e.emp_id;

Output

+--------------+-------------+-------------+
| employee     | department  | manager     |
+--------------+-------------+-------------+
| Anita Desai  | Management  | NULL        |
| Rohit Mehta  | Engineering | Anita Desai |
| Sana Iqbal   | Engineering | Rohit Mehta |
| Dev Patel    | Engineering | Rohit Mehta |
| Priya Sharma | Engineering | Rohit Mehta |
| Farhan Ali   | Sales       | Anita Desai |
| Lata Joshi   | Sales       | Farhan Ali  |
| Tom Mathew   | Sales       | Farhan Ali  |
| Kavya Reddy  | HR          | Anita Desai |
| Imran Shaikh | HR          | Kavya Reddy |
+--------------+-------------+-------------+

Think of e and m as two copies of the table: one playing "employee", one playing "manager". A LEFT JOIN keeps Anita, who has no manager.

Employees paid close to their manager

SELECT e.name AS employee, e.salary, m.name AS manager, m.salary AS manager_salary
FROM employees e
JOIN employees m ON m.emp_id = e.manager_id
WHERE e.salary >= m.salary * 0.7;          -- at least 70% of their manager's pay

Output

+--------------+--------+-------------+----------------+
| employee     | salary | manager     | manager_salary |
+--------------+--------+-------------+----------------+
| Rohit Mehta  | 180000 | Anita Desai |         250000 |
| Imran Shaikh |  65000 | Kavya Reddy |          90000 |
+--------------+--------+-------------+----------------+

Team size per manager

SELECT m.name AS manager, COUNT(e.emp_id) AS direct_reports
FROM employees m
JOIN employees e ON e.manager_id = m.emp_id
GROUP BY m.emp_id, m.name
ORDER BY direct_reports DESC, manager;

Output

+-------------+----------------+
| manager     | direct_reports |
+-------------+----------------+
| Anita Desai |              3 |
| Rohit Mehta |              3 |
| Farhan Ali  |              2 |
| Kavya Reddy |              1 |
+-------------+----------------+

Pairs within a group

-- Customers who live in the same city (each pair once)
SELECT a.name AS customer_1, b.name AS customer_2, a.city
FROM customers a
JOIN customers b ON a.city = b.city AND a.customer_id < b.customer_id;

Output

+------------+------------+-------+
| customer_1 | customer_2 | city  |
+------------+------------+-------+
| Asha Verma | Vikram Das | Pune  |
| Ravi Kumar | Zoya Khan  | Delhi |
+------------+------------+-------+

a.customer_id < b.customer_id stops a customer pairing with themselves and stops each pair appearing twice (A–B and B–A).

CROSS JOIN: every combination

SELECT s.size, c.colour
FROM (SELECT 'S' AS size UNION ALL SELECT 'M' UNION ALL SELECT 'L') AS s
CROSS JOIN (SELECT 'Black' AS colour UNION ALL SELECT 'White') AS c
ORDER BY c.colour, FIELD(s.size, 'S', 'M', 'L');

Output

+------+--------+
| size | colour |
+------+--------+
| S    | Black  |
| M    | Black  |
| L    | Black  |
| S    | White  |
| M    | White  |
| L    | White  |
+------+--------+

3 sizes × 2 colours = 6 variants. Cross joins are handy for generating product variants, calendars (every day × every store) or test data. The result size is rows(A) × rows(B), so be careful with big tables.

Joining four tables

-- Everything Asha bought: order, date, product, category, quantity and amount
SELECT o.order_id,
       o.order_date,
       p.name                         AS product,
       COALESCE(cat.name, '(none)')   AS category,
       i.quantity,
       i.quantity * i.unit_price      AS amount
FROM customers c
JOIN orders o           ON o.customer_id = c.customer_id
JOIN order_items i      ON i.order_id = o.order_id
JOIN products p         ON p.product_id = i.product_id
LEFT JOIN categories cat ON cat.category_id = p.category_id
WHERE c.name = 'Asha Verma'
ORDER BY o.order_date, p.name;

Output

+----------+------------+-------------------+-------------+----------+--------+
| order_id | order_date | product           | category    | quantity | amount |
+----------+------------+-------------------+-------------+----------+--------+
|      101 | 2025-08-02 | Gel Pen Pack      | Stationery  |        2 | 240.00 |
|      101 | 2025-08-02 | Wireless Mouse    | Electronics |        1 | 599.00 |
|      103 | 2025-08-19 | Clean Code        | Books       |        1 | 450.00 |
|      103 | 2025-08-19 | SQL in 10 Minutes | Books       |        1 | 350.00 |
|      109 | 2025-10-21 | A5 Notebook       | Stationery  |        2 | 180.00 |
|      109 | 2025-10-21 | Gel Pen Pack      | Stationery  |        3 | 360.00 |
+----------+------------+-------------------+-------------+----------+--------+

Read a multi-table join as a path

  1. customersstart from Asha
  2. → ordersher orders (customer_id)
  3. → order_itemslines of each order (order_id)
  4. → productswhat each line is (product_id)
  5. → categoriesLEFT JOIN, so uncategorised products stay

Non-equi joins

Joins don't have to use =. Here each employee is matched to a salary band by range:

SELECT e.name, e.salary, b.band
FROM employees e
JOIN (SELECT 'Band A' AS band, 0 AS low, 89999 AS high
      UNION ALL SELECT 'Band B', 90000, 149999
      UNION ALL SELECT 'Band C', 150000, 999999) AS b
  ON e.salary BETWEEN b.low AND b.high
WHERE e.department IN ('HR', 'Management')
ORDER BY e.salary DESC;

Output

+--------------+--------+--------+
| name         | salary | band   |
+--------------+--------+--------+
| Anita Desai  | 250000 | Band C |
| Kavya Reddy  |  90000 | Band B |
| Imran Shaikh |  65000 | Band A |
+--------------+--------+--------+

Practice

Try it yourself

  1. List employees who report to Rohit Mehta.

    Show solution
    SELECT e.name, e.salary
    FROM employees e
    JOIN employees m ON m.emp_id = e.manager_id
    WHERE m.name = 'Rohit Mehta'
    ORDER BY e.salary DESC, e.name;

    Output

    +--------------+--------+
    | name         | salary |
    +--------------+--------+
    | Dev Patel    | 120000 |
    | Sana Iqbal   | 120000 |
    | Priya Sharma |  95000 |
    +--------------+--------+
  2. For each category, show the customers who bought from it (each name once).

    Show solution
    SELECT cat.name AS category, GROUP_CONCAT(DISTINCT c.name ORDER BY c.name SEPARATOR ', ') AS customers
    FROM categories cat
    JOIN products p     ON p.category_id = cat.category_id
    JOIN order_items i  ON i.product_id = p.product_id
    JOIN orders o       ON o.order_id = i.order_id
    JOIN customers c    ON c.customer_id = o.customer_id
    GROUP BY cat.category_id, cat.name
    ORDER BY cat.name;

    Output

    +-------------+-----------------------------------------------+
    | category    | customers                                     |
    +-------------+-----------------------------------------------+
    | Books       | Asha Verma, Meera Nair, Neha Gupta, Zoya Khan |
    | Electronics | Asha Verma, Kiran Rao, Meera Nair, Ravi Kumar |
    | Stationery  | Asha Verma, Zoya Khan                         |
    +-------------+-----------------------------------------------+

Quick check

What is a self join?

Joining a table to itself using two aliases, e.g. employees and their managers.

How many rows does a CROSS JOIN of 4 and 5 rows return?

20.

Why use a.id < b.id when pairing rows?

To exclude self-pairs and list each pair only once.

Summary

  • A self join uses two aliases of one table – perfect for hierarchies and pairs.
  • CROSS JOIN returns every combination (rows × rows).
  • Read multi-table joins as a path; LEFT JOIN the optional links; joins can use ranges.

Interview practice: SQL queries asked in interviews.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Popular