SQL GROUP BY and HAVING

SQL GROUP BY splits rows into groups that share the same value, then runs aggregate functions on each group. Instead of one total for the whole table you get one row per country, per category or per month.

HAVING then filters those groups, just like WHERE filters rows.

GROUP BY One Column

Count customers in each country.

SELECT Country, COUNT(*) AS customers
FROM Customers
GROUP BY Country
ORDER BY customers DESC, Country;
Country  | customers
---------+----------
Pakistan | 3
UK       | 2
USA      | 2
Canada   | 1
Spain    | 1
UAE      | 1
(6 rows)

GROUP BY with SUM

Total units in stock per product category.

SELECT Category, SUM(Stock) AS units
FROM Products
GROUP BY Category
ORDER BY units DESC;
Category   | units
-----------+------
Snacks     | 560
Beverages  | 380
Grains     | 320
Oils       | 45
Sweeteners | 0
(5 rows)

Filtering Groups with HAVING

Show only countries with more than one customer. You cannot write this with WHERE, because the count only exists after grouping.

SELECT Country, COUNT(*) AS customers
FROM Customers
GROUP BY Country
HAVING COUNT(*) > 1;
Country  | customers
---------+----------
Pakistan | 3
UK       | 2
USA      | 2
(3 rows)

WHERE and HAVING Together

WHERE removes rows before grouping, HAVING removes groups after. Here we ignore orders before February, then keep customers with more than 10 units ordered.

SELECT CustomerID, SUM(Quantity) AS units
FROM Orders
WHERE OrderDate >= '2026-02-01'
GROUP BY CustomerID
HAVING SUM(Quantity) > 10
ORDER BY units DESC;
CustomerID | units
-----------+------
5          | 40
7          | 30
9          | 15
8          | 12
(4 rows)

Grouping by Month

substr extracts the YYYY-MM part of the date. In MySQL you would use DATE_FORMAT(OrderDate, '%Y-%m') and in SQL Server FORMAT(OrderDate, 'yyyy-MM').

SELECT substr(OrderDate, 1, 7) AS month, COUNT(*) AS orders
FROM Orders
GROUP BY month
ORDER BY month;
month   | orders
--------+-------
2026-01 | 2
2026-02 | 2
2026-03 | 2
2026-04 | 2
2026-05 | 2
(5 rows)

Frequently asked questions

What is the difference between WHERE and HAVING in SQL?

WHERE filters individual rows before they are grouped. HAVING filters groups after aggregation, so it can use functions like COUNT or SUM.

Does every selected column need to be in GROUP BY?

In standard SQL, every non-aggregated column in SELECT must appear in GROUP BY. MySQL with ONLY_FULL_GROUP_BY enforces this; SQLite is more relaxed but the result can be ambiguous.

Can I GROUP BY multiple columns?

Yes. GROUP BY Country, City creates one group for each unique country and city combination.