WHERE filters individual rows, but once you have grouped data, you often need to filter the groups themselves: only teams that scored over five hundred runs, only players with more than ten innings, only venues hosting at least three matches. These conditions are about aggregate values that do not exist until grouping completes, and HAVING is the clause built precisely for them.
Trying to express a group-level condition in WHERE simply fails, because WHERE runs before any aggregate is computed. HAVING fills that gap by applying its condition after GROUP BY has formed the groups and the aggregates have been calculated. It is, in effect, the WHERE clause for summary rows rather than raw rows, operating one level higher in the query.
Understanding the clean division of labour between WHERE and HAVING is what separates analysts who fight the engine from those who flow with it. Each clause has a distinct job at a distinct stage, and using the right one not only makes queries correct but often makes them dramatically faster, since filtering rows early in WHERE shrinks the work HAVING must consider later.