| Lesson 4 | Correlated subqueries |
| Objective | Identify the outer reference that correlates two query blocks, predict scalar-subquery results, and explain how aliases, nulls, ties, and optimizer transformations affect the statement. |
A correlated statement has several roles:
The following generic form uses o for the outer alias and i for the inner alias. The reference to
o.key_value appears inside the subquery even though alias o is defined by the containing query:
SELECT o.display_value
FROM outer_table o
WHERE o.measure_value > (
SELECT MAX(i.measure_value)
FROM inner_table i
WHERE i.key_value = o.key_value
);
The correlation condition is i.key_value = o.key_value. For each logical outer row, it restricts the inner aggregate to rows with the
related key. Correlation describes this dependency between query blocks; it does not by itself guarantee that the inner query returns one row.
The correlated Pet Store query can be read in five steps:
p.pcl.MAX(pcl.log_datetime) produces the latest matching log datetime as a scalar value.Product updated after its latest care-log entry in the Pet Store sample data
PRODUCT_NAME ------------ Puppy
The result table reflects the supplied Pet Store sample. The ORDER BY makes the presentation deterministic if more products qualify. The correlation condition, rather than the order of the SQL clauses, establishes the relationship between each product and its care logs.
The subquery is used on the right side of >, so it must behave as a scalar expression. A scalar subquery returns one selected column and
no more than one row. Its possible outcomes are:
NULL.
Correlation does not enforce this rule. The MAX aggregate makes the care-log subquery produce one aggregate row. Without
GROUP BY, MAX still produces one row when no inner rows match, but its value is null.
Use single-value operators such as =, >, <, >=, <=, or <> only when
the subquery is scalar. Use IN, ANY, or ALL when several comparison values are intended. Use
EXISTS when the question concerns whether a related row is present rather than the value that row supplies.
A second example compares each employee's salary with a value calculated for that employee's department. The sample requires only the
EMPLOYEES table; a separate departments table is not needed.
| EMPLOYEE_ID | NAME | SALARY | DEPARTMENT_ID |
|---|---|---|---|
| 1 | John | 5000 | 10 |
| 2 | Jane | 6000 | 10 |
| 3 | Mark | 5500 | 20 |
| 4 | Lucy | 4500 | 20 |
SELECT e.employee_id,
e.name,
e.salary,
e.department_id
FROM employees e
WHERE e.salary = (
SELECT MAX(peer.salary)
FROM employees peer
WHERE peer.department_id = e.department_id
)
ORDER BY e.department_id,
e.employee_id;
Alias e represents the candidate employee in the outer block. Alias peer represents employees considered by the inner
block. For each logical candidate, the correlation condition limits the aggregate to peers in the same department. The equality retains candidates
whose salary equals that departmental maximum.
| EMPLOYEE_ID | NAME | SALARY | DEPARTMENT_ID |
|---|---|---|---|
| 2 | Jane | 6000 | 10 |
| 3 | Mark | 5500 | 20 |
The query returns every employee tied for the maximum. If two employees in department 10 both earn 6000, both satisfy the equality. It does not
arbitrarily choose one employee. An employee whose department_id is null does not match peers through ordinary equality, because
NULL = NULL is unknown.
The maximum-per-department rule can also be written by calculating departmental maxima first and joining them to employees:
SELECT e.employee_id,
e.name,
e.salary,
e.department_id
FROM employees e
JOIN (
SELECT department_id,
MAX(salary) AS max_salary
FROM employees
GROUP BY department_id
) dept_max
ON dept_max.department_id = e.department_id
AND dept_max.max_salary = e.salary
ORDER BY e.department_id,
e.employee_id;
For the stated rule and non-null department matches, this formulation produces the same sample result and preserves salary ties. It is an alternative expression of the relationship, not a universal performance improvement. The optimizer may also transform a nested statement without the developer manually rewriting it.
In the next lesson, EXISTS and NOT EXISTS will express questions about whether related rows are present or absent.