sql_having
SQL HAVING Clause: Filtering Aggregated Results
Section titled “SQL HAVING Clause: Filtering Aggregated Results”The Role of the HAVING Clause
Section titled “The Role of the HAVING Clause”In SQL, the HAVING clause is used to filter groups created by the GROUP BY clause. It’s similar to the WHERE clause, but with a key difference: WHERE filters individual rows before they are grouped and aggregated, while HAVING filters entire groups after aggregation functions (like COUNT(), SUM(), AVG(), MIN(), MAX()) have been applied.
Why HAVING? Because WHERE Can’t See Aggregates
You cannot use aggregate functions directly in a WHERE clause. The WHERE clause operates on row-level data. By the time aggregate functions are computed, the WHERE clause filtering has already occurred. The HAVING clause was introduced specifically to address this need for filtering based on the results of aggregate functions.
SQL HAVING Syntax
Section titled “SQL HAVING Syntax”The HAVING clause is typically used in conjunction with GROUP BY:
SELECT column_name(s), aggregate_function(column_name) AS alias_nameFROM table_nameWHERE condition -- Optional: filters rows before groupingGROUP BY column_name(s)HAVING aggregate_condition; -- Filters groups after aggregationOrder of Execution (Simplified):
Section titled “Order of Execution (Simplified):”FROMtable_nameWHEREcondition (filters rows)GROUP BYcolumn_name(s) (groups rows)HAVINGaggregate_condition (filters groups)SELECTcolumn_name(s), aggregate_function (selects data)ORDER BYcolumn_name(s) (sorts final results)
Demo Database Context
Section titled “Demo Database Context”For our examples, imagine we’re working with a simplified version of the classic Northwind sample database. We’ll focus on two tables: Orders and Employees.
Sample Orders table structure and data:
OrderID | CustomerID | EmployeeID | OrderDate | ShipperID--------|------------|------------|------------|-----------10248 | 90 | 5 | 1996-07-04 | 310249 | 81 | 6 | 1996-07-05 | 110250 | 34 | 4 | 1996-07-08 | 2... more orders ...Sample Employees table structure and data:
EmployeeID | LastName | FirstName | BirthDate------------|-----------|-----------|-----------1 | Davolio | Nancy | 1968-12-082 | Fuller | Andrew | 1952-02-193 | Leverling | Janet | 1963-08-304 | Peacock | Margaret | 1958-09-195 | Buchanan | Steven | 1955-03-046 | Suyama | Michael | 1963-07-02... more employees ...SQL HAVING Examples
Section titled “SQL HAVING Examples”Example 1: Find employees who have registered more than 10 orders.
Here, we need to count orders per employee and then filter based on that count.
SELECT E.LastName, COUNT(O.OrderID) AS NumberOfOrdersFROM Orders AS OINNER JOIN Employees AS E ON O.EmployeeID = E.EmployeeIDGROUP BY E.LastNameHAVING COUNT(O.OrderID) > 10ORDER BY NumberOfOrders DESC;INNER JOINcombinesOrdersandEmployees.GROUP BY E.LastNamegroups the orders by employee last name.COUNT(O.OrderID)calculates the number of orders for each employee.HAVING COUNT(O.OrderID) > 10filters these groups, keeping only those employees with more than 10 orders.- Using table aliases (
OforOrders,EforEmployees) makes the query more readable.
Example 2: Find if employees ‘Davolio’ or ‘Fuller’ have registered more than 25 orders each.
This example combines a WHERE clause (to pre-filter for specific employees) and a HAVING clause.
SELECT E.LastName, COUNT(O.OrderID) AS NumberOfOrdersFROM Orders AS OINNER JOIN Employees AS E ON O.EmployeeID = E.EmployeeIDWHERE E.LastName IN ('Davolio', 'Fuller') -- Filter rows before groupingGROUP BY E.LastNameHAVING COUNT(O.OrderID) > 25; -- Filter groups after aggregationWHERE E.LastName IN ('Davolio', 'Fuller')first selects only orders handled by Davolio or Fuller.- Then,
GROUP BY E.LastNamegroups these filtered orders. - Finally,
HAVING COUNT(O.OrderID) > 25checks if, among these pre-selected employees, any have more than 25 orders.
Common Learning Obstacles & Best Practices:
Section titled “Common Learning Obstacles & Best Practices:”- Using
WHEREfor aggregate conditions: Remember,WHEREis for row-level filtering beforeGROUP BY.HAVINGis for group-level filtering afterGROUP BY. - Using
HAVINGwithoutGROUP BY: While some DBMS might allowHAVINGwithout an explicitGROUP BY(treating the whole table as a single group), it’s standard practice and clearer to useHAVINGwithGROUP BY. - Column aliases in
HAVING: Standard SQL requires repeating the aggregate function in theHAVINGclause (e.g.,HAVING COUNT(O.OrderID) > 10). However, many modern DBMS (like MySQL, PostgreSQL) allow you to use the alias defined in theSELECTlist (e.g.,HAVING NumberOfOrders > 10). For maximum portability, repeat the function. - Performance: If a condition can be applied before grouping (i.e., it doesn’t involve an aggregate function), it’s generally more efficient to use
WHERErather thanHAVING.