6.3 Filtering Groups with the HAVING Clause
Key Takeaways
- The HAVING clause filters aggregated group results after grouping, whereas the WHERE clause filters individual rows before grouping.
- The standard SQL logical execution lifecycle proceeds: FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY.
- In Oracle SQL, the HAVING clause can syntactically appear either before or after the GROUP BY clause, though placing it after is standard practice.
- Group functions are strictly prohibited in the WHERE clause (ORA-00934) but are fully supported and standard in the HAVING clause.
- Group functions can be nested to a maximum of two levels (e.g., MAX(AVG(salary))); nested group functions strictly require a GROUP BY clause and forbid unaggregated columns or other expressions in the SELECT list.
6.3 Filtering Groups with the HAVING Clause
When writing summary reports in SQL, developers frequently need to filter output based on aggregated metrics—such as finding departments with an average salary exceeding $8,000, or identifying product categories with more than 50 orders. Because the WHERE clause filters individual records before rows are grouped, SQL provides the HAVING clause specifically to filter summarized groups after aggregation occurs.
Understanding the precise differences between WHERE and HAVING, their logical execution sequence, and the strict rules governing nested group functions is essential for mastering the Oracle Database SQL (1Z0-071) exam.
Purpose and Mechanics of the HAVING Clause
The HAVING clause specifies search conditions for groups rather than individual rows. When a query contains a HAVING clause, Oracle evaluates the group condition against each group formed by the GROUP BY clause. Only groups that satisfy the HAVING predicate are included in the final result.
-- Example: Filtering departments with an average salary greater than 8000
SELECT department_id, COUNT(*) AS employee_count, AVG(salary) AS average_salary
FROM employees
GROUP BY department_id
HAVING AVG(salary) > 8000
ORDER BY average_salary DESC;
SQL Logical Query Execution Sequence
To understand why WHERE cannot filter on group functions while HAVING can, you must master the Logical Query Processing Lifecycle of an Oracle SQL statement:
+-----------------------------------------------------------------------------------+
| SQL LOGICAL EXECUTION LIFECYCLE |
| |
| [1. FROM / JOIN] -> Identify tables, resolve joins, build raw dataset |
| | |
| v |
| [2. WHERE] -> Filter individual rows (Pre-aggregation filter) |
| | |
| v |
| [3. GROUP BY] -> Partition remaining rows into summary groups |
| | |
| v |
| [4. HAVING] -> Filter summarized groups (Post-aggregation filter) |
| | |
| v |
| [5. SELECT] -> Evaluate expressions, group functions, assign aliases |
| | |
| v |
| [6. ORDER BY] -> Sort the final result set (Aliases & positions valid) |
+-----------------------------------------------------------------------------------+
Step-by-Step Lifecycle Analysis:
FROM&JOIN: Oracle identifies the source tables, views, or inline views and performs Cartesian products or table joins.WHERE: Evaluates single-row predicates. Rows evaluating toFALSEorUNKNOWNare eliminated. Group functions cannot be used here because groups have not yet been formed.GROUP BY: Divides the surviving rows into discrete groups based on common values in the grouping expressions.HAVING: Applies aggregate predicates to each group. Groups evaluating toFALSEorUNKNOWNare dropped.SELECT: Projects the requested columns, computes scalar expressions, calculates final group function values, and assigns column aliases.ORDER BY: Sorts the resulting records. BecauseORDER BYexecutes last, it can reference column aliases and numeric column positions.
Side-by-Side Comparison: WHERE vs. HAVING
| Comparison Aspect | WHERE Clause | HAVING Clause |
|---|---|---|
| Primary Purpose | Filters individual base rows before grouping. | Filters summarized groups after grouping. |
| Execution Timing | Executes before GROUP BY. | Executes after GROUP BY. |
| Group Functions Allowed? | NO. Raises ORA-00934: group function is not allowed here. | YES. Designed specifically for group functions (e.g., HAVING SUM(salary) > 50000). |
| Non-Aggregate Columns | Can reference any column from the FROM tables. | Can only reference columns present in the GROUP BY clause. |
| Column Aliases Allowed? | NO. Raises ORA-00904: invalid identifier. | NO. Raises ORA-00904: invalid identifier. |
| Performance Impact | Reduces dataset size early, minimizing memory and CPU usage during grouping. | Filters groups after grouping work is completed. |
| Best Practice Rule | Always use WHERE for row-level conditions (department_id != 40) rather than HAVING. | Use HAVING strictly for conditions involving aggregate functions. |
Combining WHERE and HAVING in a Single Query
SELECT department_id, AVG(salary) AS avg_sal
FROM employees
WHERE hire_date >= DATE '2005-01-01' -- Filters base rows BEFORE grouping
GROUP BY department_id
HAVING COUNT(*) >= 5 -- Filters groups AFTER grouping
AND AVG(salary) > 6000
ORDER BY avg_sal DESC;
In this query:
- Employees hired before
2005-01-01are discarded in theWHEREstep. - The remaining employees are grouped by
department_id. - The
HAVINGclause keeps only departments with at least 5 qualifying employees whose average salary exceeds 6000.
Syntax Flexibility: Placement of HAVING in Oracle SQL
While ANSI SQL conventions and industry best practices place the HAVING clause immediately after the GROUP BY clause, Oracle SQL syntactically permits the HAVING clause to precede the GROUP BY clause:
-- FULLY VALID IN ORACLE SQL: HAVING before GROUP BY
SELECT department_id, MAX(salary)
FROM employees
HAVING MAX(salary) > 10000
GROUP BY department_id;
Both syntactic arrangements produce identical execution plans and results. Furthermore, Oracle permits a HAVING clause without any GROUP BY clause, treating the entire table as a single group:
-- VALID: HAVING without GROUP BY
SELECT AVG(salary)
FROM employees
HAVING AVG(salary) > 5000;
Exam Tip: If an exam question asks whether placing
HAVINGbeforeGROUP BYresults in a compilation error, remember that in Oracle SQL it is 100% syntactically legal.
Restrictions on HAVING Predicates
- Non-aggregate columns in HAVING: Any column referenced in the
HAVINGclause that is not enclosed inside a group function must appear in theGROUP BYclause.
-- VALID: department_id is in the GROUP BY clause
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING department_id IN (10, 20, 30);
-- INVALID: job_id is NOT in the GROUP BY clause -> Causes ORA-00979
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING job_id = 'IT_PROG';
- HAVING Aggregate Independence: Aggregate expressions used in
HAVINGdo not need to appear in theSELECTlist:
-- FULLY VALID: MIN(salary) is in HAVING but not in SELECT
SELECT department_id, AVG(salary)
FROM employees
GROUP BY department_id
HAVING MIN(salary) > 2500;
Nesting Group Functions
Oracle SQL allows aggregate functions to be nested inside other aggregate functions—for instance, to calculate the maximum department average salary.
-- Calculate the highest average department salary
SELECT MAX(AVG(salary))
FROM employees
GROUP BY department_id;
The Strict Rules of Nested Group Functions on 1Z0-071
+-----------------------------------------------------------------------------------+
| NESTED GROUP FUNCTION RULES MATRIX |
+---------------------------------------+-------------------------------------------+
| Rule | Specification & Constraint |
+---------------------------------------+-------------------------------------------+
| Maximum Nesting Depth | Exactly TWO levels (e.g., MAX(AVG(col))). |
| | 3 levels raises ORA-00935. |
+---------------------------------------+-------------------------------------------+
| GROUP BY Requirement | A GROUP BY clause is MANDATORY. |
| | Omission raises ORA-00978. |
+---------------------------------------+-------------------------------------------+
| SELECT List Restriction | The SELECT list may ONLY contain the |
| | nested group function itself. No |
| | individual columns or un-nested group |
| | functions are permitted. |
+---------------------------------------+-------------------------------------------+
Why Columns Cannot Appear with Nested Group Functions
Consider the query:
-- ILLEGAL: Causes ORA-00937: not a single-group group function
SELECT department_id, MAX(AVG(salary))
FROM employees
GROUP BY department_id;
Analysis:
AVG(salary)grouped bydepartment_idproduces multiple rows (one average per department).MAX(AVG(salary))collapses those department averages into a single scalar value across the entire table.- Because the outer
MAXreduces the result to a single row, Oracle cannot display individualdepartment_idvalues alongside it without raisingORA-00937.
3-Level Nesting is Prohibited
-- ILLEGAL: Causes ORA-00935: group function is nested too deeply
SELECT SUM(MAX(AVG(salary)))
FROM employees
GROUP BY department_id;
Which statement correctly describes the difference between the WHERE clause and the HAVING clause in Oracle SQL?
Examine the following SQL statement: SELECT department_id, MAX(AVG(salary)) FROM employees GROUP BY department_id; What is the result of executing this statement?
Which of the following statements regarding the HAVING clause in Oracle SQL is TRUE?