7.2 Non-Equijoins, Self Joins, and Cartesian Products
Key Takeaways
- A non-equijoin links tables using relational comparison operators other than equality (=), such as BETWEEN ... AND ..., >=, <=, >, <, or !=.
- A common application of non-equijoins is mapping continuous numeric values (like employee salaries) into categorical ranges (such as salary grade levels in a JOB_GRADES table).
- A self join joins a table to itself to model recursive or hierarchical parent-child relationships, strictly requiring distinct table aliases to differentiate the instances.
- A CROSS JOIN computes the Cartesian product of two tables, generating M x N rows by pairing every row from the first table with every row from the second table.
- Accidental Cartesian products occur when join conditions are omitted or invalid in legacy comma syntax, causing massive result sets and severe database performance degradation.
7.2 Non-Equijoins, Self Joins, and Cartesian Products
While most relational database queries combine tables based on primary key-foreign key equality, relational database systems frequently require more sophisticated join patterns. Oracle SQL provides robust support for non-equijoins (joining on ranges or inequalities), self joins (joining a table to itself to resolve recursive hierarchies), and Cartesian products (generating all possible row permutations with CROSS JOIN).
Mastering these join varieties is essential for the 1Z0-071 exam, where questions frequently test range predicates, table aliasing requirements, and row multiplication mechanics.
Non-Equijoins
A non-equijoin is any join condition that does not use the equality operator (=). Instead, it uses operators such as BETWEEN ... AND ..., >=, <=, >, <, !=, or <>.
Real-World Use Case: Salary Grading
A classic non-equijoin scenario involves evaluating an employee's compensation against a grading bracket table. Consider a JOB_GRADES table that defines salary grade bands:
JOB_GRADES TABLE:
+-------------+------------+-------------+
| GRADE_LEVEL | LOWEST_SAL | HIGHEST_SAL |
+-------------+------------+-------------+
| A | 1000 | 2999 |
| B | 3000 | 5999 |
| C | 6000 | 9999 |
| D | 10000 | 14999 |
| E | 15000 | 24999 |
| F | 25000 | 40000 |
+-------------+------------+-------------+
Because the EMPLOYEES table stores an exact SALARY amount (e.g., 6500) rather than a GRADE_LEVEL, we cannot use an equality join. Instead, we use a non-equijoin with BETWEEN ... AND ...:
-- ANSI SQL Non-Equijoin Syntax
SELECT
e.employee_id,
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)
ORDER BY e.salary DESC;
NON-EQUIJOIN RESULT SET (Sample):
+-------------+-----------+--------+-------------+
| EMPLOYEE_ID | LAST_NAME | SALARY | GRADE_LEVEL |
+-------------+-----------+--------+-------------+
| 100 | King | 24000 | E |
| 101 | Kochhar | 17000 | E |
| 102 | De Haan | 17000 | E |
| 103 | Hunold | 9000 | C |
| 104 | Ernst | 6000 | C |
| 107 | Lorentz | 4200 | B |
+-------------+-----------+--------+-------------+
Non-Equijoins Using Comparison Operators
Non-equijoins can also be written using inequality comparison operators:
-- Equivalent query using relational operators
SELECT e.last_name, e.salary, g.grade_level
FROM employees e
JOIN job_grades g ON (e.salary >= g.lowest_sal AND e.salary <= g.highest_sal);
Exam Trap: Remember that
BETWEEN lower_bound AND upper_boundis inclusive. The lower bound must always be listed first. Writinge.salary BETWEEN g.highest_sal AND g.lowest_salwill evaluate to false for all rows and return zero records.
Self Joins (Recursive / Hierarchical Joins)
A self join is a join in which a table is joined with itself. This technique is used when a table contains a recursive relationship where a column in a row references the primary key of another row within the same table.
The Employee-Manager Hierarchy
In the EMPLOYEES table, each employee record has a MANAGER_ID column that contains the EMPLOYEE_ID of their supervisor:
+-----------------------------------------------------------------------------------+
| RECURSIVE EMPLOYEE-MANAGER RELATION |
| |
| EMPLOYEES (Worker Copy: 'w') EMPLOYEES (Manager Copy: 'm') |
| +--------+-----------+------------+ +--------+-----------+ |
| | EMP_ID | LAST_NAME | MANAGER_ID | | EMP_ID | LAST_NAME | |
| +--------+-----------+------------+ +--------+-----------+ |
| | 101 | Kochhar | 100 |----------->| 100 | King | |
| | 102 | De Haan | 100 |----------->| 100 | King | |
| | 103 | Hunold | 102 |----------->| 102 | De Haan | |
| | 104 | Ernst | 103 |----------->| 103 | Hunold | |
| | 100 | King | NULL | (No match) | ... | ... | |
| +--------+-----------+------------+ +--------+-----------+ |
+-----------------------------------------------------------------------------------+
Syntax and Mandatory Table Aliases
To perform a self join, you must assign distinct table aliases to the two references of the same table so that Oracle can distinguish between the two roles (e.g., worker w and manager m).
SELECT
w.employee_id AS emp_id,
w.last_name AS employee_name,
m.employee_id AS mgr_id,
m.last_name AS manager_name
FROM employees w
JOIN employees m ON (w.manager_id = m.employee_id)
ORDER BY w.employee_id;
SELF JOIN OUTPUT (Sample):
+--------+---------------+--------+--------------+
| EMP_ID | EMPLOYEE_NAME | MGR_ID | MANAGER_NAME |
+--------+---------------+--------+--------------+
| 101 | Kochhar | 100 | King |
| 102 | De Haan | 100 | King |
| 103 | Hunold | 102 | De Haan |
| 104 | Ernst | 103 | Hunold |
| 105 | Austin | 103 | Hunold |
+--------+---------------+--------+--------------+
Key Rules for Self Joins on 1Z0-071
- Mandatory Table Aliases: Without distinct aliases Oracle cannot tell the two copies of the table apart, so every shared column reference fails with
ORA-00918: column ambiguously defined. - Mandatory Column Qualification: Every column referenced in the
SELECT,ON,WHERE, orORDER BYclause must be prefixed with the appropriate alias (w.orm.). - Exclusion of the Root Record: In an inner self join, employee 100 (King) has
MANAGER_ID = NULL. BecauseNULL = 100evaluates toUNKNOWN, King is excluded from the result set. (To include King, aLEFT OUTER JOINis required).
Cartesian Products & The CROSS JOIN Clause
A Cartesian product (or Cartesian join) is formed when every single row of the first table is paired with every single row of the second table.
Row Count Formula
If Table A contains $M$ rows and Table B contains $N$ rows, their Cartesian product produces:
For three tables with $M, N, P$ rows:
+-----------------------------------------------------------------------------------+
| CARTESIAN PRODUCT (CROSS JOIN) |
| |
| TABLE A (3 Rows) TABLE B (2 Rows) RESULT SET (3 x 2 = 6) |
| +------+ +-------+ +------+-------+ |
| | ID_A | | VAL_B | | ID_A | VAL_B | |
| +------+ +-------+ +------+-------+ |
| | 1 |---+ +-->| X | | 1 | X | |
| | 2 |-+ | | +>| Y | | 1 | Y | |
| | 3 | | | | | +-------+ | 2 | X | |
| +------+ | +-------------|-+ | 2 | Y | |
| +---------------|-+ | 3 | X | |
| + | 3 | Y | |
| +------+-------+ |
+-----------------------------------------------------------------------------------+
ANSI CROSS JOIN Syntax
In ANSI SQL:1999, a Cartesian product is explicitly generated using the CROSS JOIN keyword without any ON or USING clause:
-- Explicit ANSI CROSS JOIN
SELECT d.department_name, l.city
FROM departments d
CROSS JOIN locations l;
If DEPARTMENTS has 27 rows and LOCATIONS has 23 rows, the query returns $27 \times 23 = \mathbf{621}$ rows.
Legacy Comma Syntax & Accidental Cartesian Joins
In legacy Oracle syntax, a Cartesian product occurs whenever you list multiple tables in the FROM clause and omit the WHERE join condition (or write an invalid join condition):
-- Legacy Comma Syntax producing Cartesian Product
SELECT e.last_name, d.department_name
FROM employees e, departments d;
-- 107 employees x 27 departments = 2,889 rows returned!
Intentional vs. Accidental Cartesian Products
+-----------------------------------------------------------------------------------+
| CARTESIAN JOIN EVALUATION |
+-----------------------------+-----------------------------------------------------+
| Intentional Use Cases | - Generating test data with millions of permutations|
| | - Matrix reporting (e.g., all Products x all Months)|
| | - Creating calendar dimension tables |
+-----------------------------+-----------------------------------------------------+
| Accidental Dangers | - Omitted WHERE join condition in legacy queries |
| | - Massive memory consumption and temp tablespace exhaustion
| | - Severe database lockups and CPU spikes |
+-----------------------------+-----------------------------------------------------+
Comparison Matrix: Join Types & Characteristics
| Join Type | Defining Mechanism | Syntax Keyword / Predicate | Output Cardinality |
|---|---|---|---|
| Equijoin | Equality matching on shared key values | JOIN ... ON (t1.id = t2.id) or USING (id) | Matched subset $\le M \times N$ |
| Non-Equijoin | Range or inequality matching | JOIN ... ON (t1.val BETWEEN t2.low AND t2.high) | Matched subset based on range |
| Self Join | Joins table to itself via distinct aliases | FROM employees w JOIN employees m ON (...) | Hierarchical pairs $\le$ row count |
| Cross Join | Unconditional pairing of all rows | CROSS JOIN or legacy FROM t1, t2 | Exact multiplication: $M \times N$ |
Examine the following query: SELECT e.last_name, e.salary, g.grade_level FROM employees e JOIN job_grades g ON (e.salary BETWEEN g.highest_sal AND g.lowest_sal); Assuming the EMPLOYEES table contains 107 rows and JOB_GRADES has valid salary bands, how many rows will this query return?
Table REGIONS contains 4 rows, COUNTRIES contains 25 rows, and LOCATIONS contains 23 rows. A developer executes: SELECT * FROM regions CROSS JOIN countries CROSS JOIN locations;. How many rows are returned in the result set?
Which requirement is MANDATORY when writing a self join in Oracle SQL?