Joins


Relational databases distribute data across tables for integrity and efficiency. These tables are linked by relationships, typically via Primary Keys and Foreign Keys. A Foreign Key in one table refers to a primary key in another, establishing a link. Referential Integrity ensures these relationships remain valid, preventing inconsistent data.

Types of SQL Joins

SQL Joins combine rows from two or more tables based on related columns.

INNER JOIN: Returns matching rows from both tables

An INNER JOIN returns only rows where there's a match in both tables based on the join condition.

Example 1: Oracle 19c SQL INNER JOIN with Two Tables

SELECT
    e.employee_id, -- Employee ID
    e.first_name,  -- First name
    e.last_name,   -- Last name
    d.department_name -- Department name
FROM
    employees e -- Alias 'e' for employees
INNER JOIN
    departments d ON e.department_id = d.department_id; -- Join on department ID

Explanation: This query joins employees and departments, returning employee details and their department names where department_id matches in both tables.


Example 2: Oracle 19c SQL INNER JOIN with Multiple Conditions

SELECT
    p.product_name,    -- Product name
    o.order_id,        -- Order ID
    oi.quantity        -- Quantity
FROM
    products p         -- Alias 'p' for products
INNER JOIN
    order_items oi ON p.product_id = oi.product_id -- Join products to order_items
INNER JOIN
    orders o ON oi.order_id = o.order_id           -- Join order_items to orders
WHERE
    o.order_date >= TRUNC(SYSDATE) - 30; -- Orders in last 30 days

Explanation: This example joins products, order_items, and orders to retrieve product, order, and quantity details for orders placed within the last 30 days.


Example 3: Oracle 19c SQL INNER JOIN with Aliases

SELECT
    c.customer_name, -- Customer name
    o.order_date,    -- Order date
    o.total_amount   -- Total order amount
FROM
    customers c      -- Alias 'c' for customers
INNER JOIN
    orders o         -- Alias 'o' for orders
ON
    c.customer_id = o.customer_id -- Join on customer ID
WHERE
    c.city = 'New York'; -- Filter for New York customers

Explanation: This query uses an INNER JOIN to combine customer and order data, filtering for orders from customers in 'New York'.


OUTER JOIN

Includes unmatched rows

OUTER JOINs retrieve all rows from one or both tables, including those without a match in the other.

LEFT OUTER JOIN (or LEFT JOIN): All rows from left table, matching from right

A LEFT OUTER JOIN returns all rows from the left table and matching rows from the right. If no match in the right table, NULLs appear for right table columns.

Example 1: Oracle 19c SQL LEFT OUTER JOIN with Employees and Departments

SELECT
    e.first_name,     -- Employee first name
    e.last_name,      -- Employee last name
    d.department_name -- Department name
FROM
    employees e       -- Left table: employees
LEFT OUTER JOIN
    departments d ON e.department_id = d.department_id; -- Join on department ID

Explanation: This query lists all employees and their department names. If an employee has no department, department_name will be NULL.


Example 2: Oracle 19c SQL LEFT OUTER JOIN to Find Unmatched Records

SELECT
    c.customer_name,  -- Customer name
    o.order_id        -- Order ID (NULL if no orders)
FROM
    customers c       -- Left table: customers
LEFT OUTER JOIN
    orders o ON c.customer_id = o.customer_id
WHERE
    o.order_id IS NULL; -- Find customers without orders

Explanation: This example finds customers who have not placed any orders by looking for NULL order IDs after the LEFT OUTER JOIN.


Example 3: Oracle 19c SQL LEFT OUTER JOIN with Aggregation

SELECT
    p.product_name,        -- Product name
    NVL(SUM(oi.quantity), 0) AS total_sold -- Total quantity sold, 0 if no sales
FROM
    products p             -- Left table: products
LEFT OUTER JOIN
    order_items oi ON p.product_id = oi.product_id
GROUP BY
    p.product_name         -- Group by product name
ORDER BY
    total_sold DESC;       -- Order by total sold

Explanation: This query lists all products and their total sales quantity. Products with no sales will show a quantity of 0 using NVL.


RIGHT OUTER JOIN (or RIGHT JOIN)

All rows from right table, matching from left

A RIGHT OUTER JOIN returns all rows from the right table and matching rows from the left. If no match in the left table, NULLs appear for left table columns.

Example 1: Oracle 19c SQL RIGHT OUTER JOIN with Departments and Employees

SELECT
    d.department_name, -- Department name
    e.first_name,      -- Employee first name
    e.last_name        -- Employee last name
FROM
    employees e        -- Left table: employees
RIGHT OUTER JOIN
    departments d ON e.department_id = d.department_id; -- Join on department ID

Explanation: This query lists all departments. If a department has no employees, employee names will be NULL.


Example 2: Oracle 19c SQL RIGHT OUTER JOIN to Show All Categories

SELECT
    c.category_name,  -- Category name
    p.product_name    -- Product name (NULL if no products)
FROM
    products p        -- Left table: products
RIGHT OUTER JOIN
    categories c ON p.category_id = c.category_id; -- Join on category ID

Explanation: This example displays all product categories. Categories without products will still appear, with NULL product names.


Example 3: Oracle 19c SQL RIGHT OUTER JOIN with Counting

SELECT
    l.location_name,    -- Location name
    COUNT(e.employee_id) AS number_of_employees -- Employee count at location
FROM
    employees e         -- Left table: employees
RIGHT OUTER JOIN
    locations l ON e.location_id = l.location_id
GROUP BY
    l.location_name     -- Group by location name
ORDER BY
    number_of_employees DESC; -- Order by employee count

Explanation: This query lists all locations and the number of employees at each. Locations without employees will show a count of 0.


FULL OUTER JOIN

All rows from both tables, matching or not

A FULL OUTER JOIN returns all rows from both tables. NULLs appear for columns from the side with no match.

Example 1: Oracle 19c SQL FULL OUTER JOIN Between Employees and Departments

SELECT
    e.first_name,      -- Employee first name
    e.last_name,       -- Employee last name
    d.department_name  -- Department name
FROM
    employees e        -- Left table: employees
FULL OUTER JOIN
    departments d ON e.department_id = d.department_id; -- Join on department ID

Explanation: This query returns all employees and all departments. Employees without departments and departments without employees are included, with NULLs where there's no match.


Example 2: Oracle 19c SQL FULL OUTER JOIN to Find All Intersections and Discrepancies

SELECT
    c.customer_name,  -- Customer name
    o.order_id,       -- Order ID
    p.product_name    -- Product name
FROM
    customers c
FULL OUTER JOIN
    orders o ON c.customer_id = o.customer_id
FULL OUTER JOIN
    order_items oi ON o.order_id = oi.order_id
FULL OUTER JOIN
    products p ON oi.product_id = p.product_id;

Explanation: This complex query uses multiple FULL OUTER JOINs to show all data across customers, orders, order items, and products, highlighting any missing links with NULL values.


Example 3: Oracle 19c SQL FULL OUTER JOIN with a Calendar Table

SELECT
    cal.calendar_date, -- Date from calendar table
    NVL(COUNT(o.order_id), 0) AS orders_count -- Order count for the date
FROM
    calendar_dates cal -- Calendar table
FULL OUTER JOIN
    orders o ON TRUNC(o.order_date) = cal.calendar_date
WHERE
    cal.calendar_date BETWEEN DATE '2025-01-01' AND DATE '2025-01-31'
GROUP BY
    cal.calendar_date
ORDER BY
    cal.calendar_date;

Explanation: This query counts orders per day in January 2025, using a FULL OUTER JOIN with a calendar table to include days with zero orders.


CROSS JOIN

Cartesian Product (All combinations of rows)

A CROSS JOIN produces a Cartesian product, combining every row from the first table with every row from the second. No ON clause is needed.

Example 1: Oracle 19c SQL CROSS JOIN for All Combinations

SELECT
    c.customer_name,  -- Customer name
    p.product_name    -- Product name
FROM
    customers c       -- First table
CROSS JOIN
    products p;       -- Second table

Explanation: This query generates all possible pairings of customers and products.


Example 2: Oracle 19c SQL CROSS JOIN for Test Data Generation

SELECT
    'User_' || TO_CHAR(LEVEL) AS user_id, -- Generated user ID
    'Item_' || TO_CHAR(p.product_id) AS item_id -- Combined product ID
FROM
    DUAL
CONNECT BY LEVEL <= 5 -- Generate 5 "users"
CROSS JOIN
    products p
WHERE
    p.product_id <= 3; -- Limit products

Explanation: This example uses a CROSS JOIN to generate test data by combining generated user IDs with a subset of product IDs.


Example 3: Oracle 19c SQL CROSS JOIN with Filtering (Effectively an INNER JOIN)

SELECT
    e.first_name,      -- Employee first name
    d.department_name  -- Department name
FROM
    employees e
CROSS JOIN
    departments d
WHERE
    e.department_id = d.department_id; -- Join condition

Explanation: This query performs a CROSS JOIN then filters the result to achieve the same output as an INNER JOIN. While functionally similar, an explicit INNER JOIN is generally preferred for clarity and efficiency.


SELF JOIN

Joining a table to itself

A SELF JOIN joins a table to itself to combine rows within the same table. Aliases are essential to distinguish between the two table instances.

Example 1: Oracle 19c SQL SELF JOIN for Employee-Manager Hierarchy

SELECT
    e.first_name || ' ' || e.last_name AS employee_name, -- Employee's full name
    m.first_name || ' ' || m.last_name AS manager_name    -- Manager's full name
FROM
    employees e        -- Instance for employees
INNER JOIN
    employees m ON e.manager_id = m.employee_id; -- Instance for managers, joined on manager_id

Explanation: This query uses a SELF JOIN on the employees table to list each employee and their direct manager.


Example 2: Oracle 19c SQL SELF JOIN to Find Employees in the Same Department

SELECT
    e1.first_name || ' ' || e1.last_name AS employee1, -- First employee
    e2.first_name || ' ' || e2.last_name AS employee2  -- Second employee
FROM
    employees e1       -- First instance of employees
INNER JOIN
    employees e2 ON e1.department_id = e2.department_id -- Same department
AND e1.employee_id <> e2.employee_id; -- Exclude self-comparison

Explanation: This example uses a SELF JOIN to find pairs of employees who work in the same department, excluding self-matches.


Example 3: Oracle 19c SQL SELF JOIN with LEFT OUTER JOIN for Managers Without Employees

SELECT
    m.first_name || ' ' || m.last_name AS manager_name,  -- Manager's name
    COUNT(e.employee_id) AS number_of_direct_reports      -- Count of direct reports
FROM
    employees m        -- Instance for managers
LEFT OUTER JOIN
    employees e ON m.employee_id = e.manager_id -- Join manager to their reports
GROUP BY
    m.employee_id, m.first_name, m.last_name
HAVING
    COUNT(e.employee_id) = 0; -- Managers with no direct reports

Explanation: This query identifies managers with no direct reports by combining a SELF JOIN with a LEFT OUTER JOIN and filtering for zero reports.


 

Oracle SQL Join Best Practices

To ensure your Oracle SQL queries are efficient and maintainable, consider these best practices when working with joins:

Use Aliases Consistently: Always use meaningful aliases for tables, especially in multi-table joins or self-joins, to improve readability and prevent ambiguity.

Specify Join Type Explicitly: Always use explicit INNER JOIN, LEFT OUTER JOIN, etc., instead of relying on older comma-separated joins with WHERE clauses, as explicit joins are clearer and often optimized better by the Oracle optimizer.

Index Join Columns: Ensure that the columns used in your join conditions (both Primary Keys and Foreign Keys) are indexed. This is crucial for query performance, especially with large tables.

Filter Early: Apply WHERE clause conditions as early as possible in your query. Filtering data before joining often reduces the number of rows processed, leading to faster execution.

Understand Data Relationships: A solid understanding of your database schema and the relationships between tables is paramount to writing correct and efficient join queries.

Test and Optimize: Always test your join queries with realistic data volumes and use Oracle's EXPLAIN PLAN or SQL Monitor to understand their execution plan and identify potential performance bottlenecks.

Be Mindful of NULLs: Remember how different join types handle NULL values in join columns. Outer joins are specifically designed to include rows with unmatched NULLs, which can be critical for certain reports.