Skip to main content

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
WHERE drops rows before they are grouped. HAVING drops whole groups after they are built. That 1 sentence is the whole lesson, and everything below is a consequence of it. Open the client with mysql -u root -p sakila before you start.

How HAVING works

Replace GROUPING_COLUMN with the column that defines the groups, TABLE_NAME with the table, AGGREGATE_FUNCTION(COLUMN_NAME) with what to work out per group, and GROUP_CONDITION with a test that applies to a whole group, such as COUNT(*) > 200. The server builds every group first, then keeps the groups where the condition is true.

Examples

1) Keep only the large groups

All 5 ratings were counted. 3 of the groups then failed the test and were dropped.

2) HAVING can use an alias

An alias names a column of the result, and by the time HAVING runs the result exists.
The same 2 rows, and shorter to write. WHERE cannot do this, because it runs before there is a result to name: the WHERE lesson shows the ERROR 1054 you get instead.

3) Both clauses in one query

Most real queries use both: WHERE to choose the rows, HAVING to choose the groups.
WHERE kept the films over 120 minutes, the grouping counted them per rating, and HAVING kept the 1 rating with more than 100 of them.

An aggregate cannot go in WHERE

Move that HAVING condition into WHERE and the server refuses it outright.
WHERE runs before the grouping, so there is no count for it to test. The same applies to the alias of an aggregate, with a different error.
Both mean the same thing and both are fixed the same way: move the condition to HAVING. ERROR 1054 on an alias in WHERE has 2 remedies, and the aggregate is what tells them apart. An alias for a plain expression can be repeated in WHERE, which the WHERE lesson shows. An alias for an aggregate cannot be repeated there at all, because of ERROR 1111 above, so it has to move to HAVING.

Which clause tests what

Put a condition in WHERE whenever it tests a row, and keep HAVING for what only makes sense once a group exists. That is also the cheaper arrangement, for reasons the MySQL GROUP BY performance guide measures.
Naming a select-list alias in HAVING is a MySQL extension. Repeat the aggregate rather than the alias when the same SQL has to run on another database.

Summary

  • Use HAVING to drop whole groups, and WHERE to drop rows.
  • Expect HAVING to run after GROUP BY, so it can test an aggregate.
  • Read ERROR 1111 as an aggregate in WHERE that belongs in HAVING.
  • Name a column alias in HAVING, which WHERE cannot do.
  • Put a condition in WHERE when it tests a row rather than a group.

See also