SQL Guide Chapter

Lesson 7 – Displaying Data from Multiple Tables Using JOINs (or: persuading your tables to talk to each other)

And look, normalized databases are great for design and terrible for reporting. All the interesting information is split across five different tables—employees here, departments there, jobs somewhere else—so a simple qu...

oracle 19c mysql notes study chapter

And look, normalized databases are great for design and terrible for reporting. All the interesting information is split across five different tables—employees here, departments there, jobs somewhere else—so a simple question like “who does what, where?” suddenly requires joins.

This lesson is about teaching your SELECT statements to navigate that mess.

You will learn to:


1. Why JOIN at All?

Because the database is normalized:

To answer questions like “list all employees with their job titles and department names”, you must join tables on related columns, typically primary key ↔ foreign key pairs:

Joins recombine normalized pieces into a single result set.


2. ANSI JOIN Types Overview

Oracle and MySQL both support ANSI join syntax, including:

The ON and USING forms are what you’ll rely on the most; NATURAL JOIN is convenient but can be dangerously magical.


3. INNER JOIN with ON – The Workhorse

Standard pattern:

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    JOIN   departments d
           ON e.department_id = d.department_id;

Key points:

You can include the INNER keyword explicitly:

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    INNER JOIN departments d
            ON e.department_id = d.department_id;

Same result, just more explicit.

3.1 Table aliases and ambiguous columns

When both tables have a column with the same name (e.g., department_id), you must qualify it:

SELECT department_id
    FROM   employees, departments;
    -- ERROR: column ambiguously defined

Fix with table (or alias) prefixes:

SELECT e.department_id,
           d.department_id
    FROM   employees  e
    JOIN   departments d
           ON e.department_id = d.department_id;

Use meaningful aliases (e, emp, d, dept) so your future self can understand the join.

Note: in Oracle, you may not use AS for table aliases:

FROM employees AS e   -- invalid in Oracle
    FROM employees e      -- valid

4. USING and NATURAL JOIN – Shortcuts with Caveats

4.1 JOIN ... USING

If both tables have a column with the same name and compatible type, you can use USING:

SELECT last_name,
           department_name,
           department_id
    FROM   employees  e
    JOIN   departments d
           USING (department_id);

Rules:

4.2 NATURAL JOIN

NATURAL JOIN automatically joins on all columns that:

Example:

SELECT last_name,
           department_name
    FROM   employees
    NATURAL JOIN departments;

Behind the scenes, Oracle looks for all identically named columns (e.g., department_id, manager_id) and joins on all of them. So if both tables share department_id and manager_id, you’re effectively doing:

... JOIN ... USING (department_id, manager_id)

This can dramatically reduce the number of rows returned compared to a join on just one column.

Caution: NATURAL JOIN is convenient but fragile:

Use it sparingly and only when you truly control the schema.


5. Joining More Than Two Tables

You can keep adding joins as long as the relationships make sense.

Example – employees, departments, and locations:

SELECT e.last_name,
           d.department_name,
           l.city
    FROM   employees  e
    JOIN   departments d
           ON e.department_id = d.department_id
    JOIN   locations  l
           ON d.location_id = l.location_id;

Here:

You can keep going for as many tables as your query (and your brain) can handle.

Conditions unrelated to the joins can go either in the ON clauses or in a trailing WHERE:

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    JOIN   departments d
           ON e.department_id = d.department_id
    WHERE  e.manager_id = 149;

or:

... JOIN departments d
         ON e.department_id = d.department_id
        AND e.manager_id = 149;

Both are valid; the key is to keep the join condition clear and separate from filter conditions.


6. Self-Joins – When a Table Is Its Own Boss

Sometimes the relationship you care about is entirely inside one table. Classic example: employees and their managers.

To pair each employee with their manager’s last name, you join the table to itself:

SELECT e.last_name AS emp,
           m.last_name AS mgr
    FROM   employees e
    JOIN   employees m
           ON e.manager_id = m.employee_id;

This is a self-join, and it’s how you turn a single table into a hierarchy (or at least an org chart).


7. Nonequijoins – When the Join Condition Is a Range

Not all relationships are equality-based. Sometimes you have ranges.

Example: JOB_GRADES table:

GRADE_LEVEL  LOWEST_SAL  HIGHEST_SAL
    -----------  ----------  ----------
    A            1000        2999
    B            3000        5999
    C            6000        9999
    D            10000       14999
    E            15000       24999

You want to assign each employee a grade based on their salary.

Nonequijoin:

SELECT e.last_name,
           e.salary,
           g.grade_level
    FROM   employees   e
    JOIN   job_grades  g
           ON e.salary BETWEEN g.lowest_sal AND g.highest_sal;

This is called a nonequijoin because the join condition uses BETWEEN (a range) instead of =.


8. OUTER JOINs – Bringing Back the Lonely Rows

INNER JOIN only returns rows that have a match on both sides. OUTER JOINs return matched rows plus the unmatched rows from one or both tables.

8.1 LEFT OUTER JOIN

All rows from the left table, and matching rows from the right; unmatched right‑side columns are NULL.

Example – all employees, even those without a department:

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    LEFT  JOIN departments d
           ON e.department_id = d.department_id;

8.2 RIGHT OUTER JOIN

All rows from the right table, and matching rows from the left.

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    RIGHT JOIN departments d
           ON e.department_id = d.department_id;

8.3 FULL OUTER JOIN (Oracle only)

All rows from both tables:

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    FULL  JOIN departments d
           ON e.department_id = d.department_id;

This shows:

MySQL does not support FULL OUTER JOIN directly; you emulate it with UNION of left and right joins.


9. CROSS JOIN / Cartesian Product – The “Everything with Everything” Join

A CROSS JOIN (or an INNER JOIN without a condition) produces the Cartesian product:

SELECT e.last_name,
           d.department_name
    FROM   employees  e
    CROSS JOIN departments d;

If there are:

You get 107 × 28 = 2,996 rows.

The first 107 rows might show every employee “working” in department 10, the next 107 in department 20, and so on. This is rarely what you actually want, but it’s excellent at stress-testing your client tool.

Conceptually, a forgotten join condition with FROM employees e, departments d and a filterless WHERE clause can accidentally create the same cartesian product. Treat that as a smell.


10. MySQL Notes

All of the ANSI join patterns you’ve seen apply to MySQL too:

MySQL does not support FULL OUTER JOIN directly; use LEFT JOIN ... UNION ... RIGHT JOIN minus the intersection if needed.


11. What You Should Now Be Able to Do

By the end of this lesson, you should be able to:

You can now ask questions that span employees + jobs + departments + locations, which is exactly the point where people start calling your queries “the reporting layer”.

← previous view source markdown next →