So far, we’ve learned how to read data with SELECT, filter rows with WHERE, and sort results with ORDER BY. But what if you don’t just want to see every row — what if you want to summarize data? That’s where GROUP BY and aggregate functions come in.
Basic Aggregate Function Syntax
SELECT column1,
AGGREGATE_FUNCTION(column2)
FROM schema_name.table_name
GROUP BY column1;GROUP BYtakes rows that share the same value in one or more columns and groups them together.- Aggregate functions like
COUNT,SUM, orAVGare then applied to each group.
Universal Aggregate Functions
The functions listed below are supported by all major relational databases. Some platforms also provide additional aggregate functions (like STRING_AGG in PostgreSQL or GROUP_CONCAT in MySQL), but the core aggregation functions shown here will work everywhere.
COUNT()→ number of rowsCOUNT(*)orCOUNT(1)counts all rowsCOUNT(column_name)counts only non-NULL values
SUM()→ total of numeric values- Must be applied to a numeric column type
- Ignores NULL values
AVG()→ average of numeric values- Must be applied to a numeric column type
- Ignores NULL values, which may skew results if not handled properly
MIN()→ smallest value- Can be applied to numeric, text, or date columns
MAX()→ largest value- Can be applied to numeric, text, or date columns
It doesn’t make much sense to group on unique columns, that will return a single record for each row. For these examples we’ve expanded the paintings table used in previous posts to properly demonstrate aggregate functions.
Table: Gallery.Paintings
| PaintingID | Title | Artist | YearCreated | CountryOfOrigin |
|---|---|---|---|---|
| 1 | The Great Wave of Kanagawa | Katsushika Hokusai | 1831 | Japan |
| 2 | The Scream | Edvard Munch | 1893 | Norway |
| 3 | The Persistence of Memory | Salvador Dalí | 1931 | Spain |
| 4 | Mona Lisa | Leonardo da Vinci | 1503 | Italy |
| 5 | Girl with a Pearl Earring | Johannes Vermeer | 1665 | Netherlands |
| 6 | Nighthawks | Edward Hopper | 1942 | United States |
| 7 | The Starry Night | Vincent van Gogh | 1889 | Netherlands |
| 8 | Fine Wind, Clear Morning | Katsushika Hokusai | 1830 | Japan |
| 9 | Thunderstorm Beneath the Summit | Katsushika Hokusai | 1830 | Japan |
| 10 | The Sick Child | Edvard Munch | 1885 | Norway |
| 11 | The Dance of Life | Edvard Munch | 1899 | Norway |
| 12 | Swans Reflecting Elephants | Salvador Dalí | 1937 | Spain |
| 13 | Metamorphosis of Narcissus | Salvador Dalí | 1937 | Spain |
| 14 | The Last Supper | Leonardo da Vinci | 1498 | Italy |
| 15 | The Milkmaid | Johannes Vermeer | 1658 | Netherlands |
| 16 | View of Delft | Johannes Vermeer | 1661 | Netherlands |
| 17 | Sunflowers | Vincent van Gogh | 1888 | Netherlands |
| 18 | Café Terrace at Night | Vincent van Gogh | 1888 | Netherlands |
| 19 | Automat | Edward Hopper | 1927 | United States |
| 20 | Early Sunday Morning | Edward Hopper | 1930 | United States |
Example: Count paintings per country
SELECT CountryOfOrigin,
COUNT(1) AS PaintingCount
FROM Gallery.Paintings
GROUP BY CountryOfOrigin
ORDER BY PaintingCount DESC,
CountryOfOrigin ASC;Result:
| CountryOfOrigin | PaintingCount |
|---|---|
| Netherlands | 6 |
| Japan | 3 |
| Norway | 3 |
| Spain | 3 |
| United States | 3 |
| Italy | 2 |
Making Results Easier to Read with Aliases
Notice the AS PaintingCount in the example above? The AS keyword is used to define a column alias.
An alias is a temporary name you give to a column or calculation in your query results. It doesn’t change the underlying table—it just assigns the column a clear, descriptive identifier, making your output easier to read.
Without an alias, the column name would appear as a generic name determined by the database platform such as (No column name), which isn’t very user-friendly.
💡Tip: In most databases, AS is optional. You could simply write COUNT(*) OrderCount and get the same result. However, using AS is considered best practice because it makes your queries easier for someone else to follow.
Aliases with Spaces
Best practice is to keep aliases simple, without spaces. However, sometimes reporting requirements ask for more “friendly” names, like Painting Count instead of PaintingCount.
You can do this by enclosing the alias in either double quotes (" ") or square brackets ([ ]), depending on your database platform.
- SQL Server → square brackets
[ ]or double quotes" "
SELECT CountryOfOrigin,
COUNT(1) AS [Painting Count]
FROM Gallery.Paintings
GROUP BY CountryOfOrigin;- Oracle / PostgreSQL / MySQL / SQLite → double quotes
" "
SELECT CountryOfOrigin,
COUNT(1) AS "Painting Count"
FROM Gallery.Paintings
GROUP BY CountryOfOrigin;Result:
| CountryOfOrigin | Painting Count |
|---|---|
| Italy | 2 |
| Japan | 3 |
| Netherlands | 6 |
| Norway | 3 |
| Spain | 3 |
| United States | 3 |
📌 Important Note: Using spaces can make queries harder to maintain, because you’ll need to quote the alias again if you reference it in an outer query. Best practice is to stick with simple names unless there’s a strong reporting need.
What About Single Quotes?
Single quotes (' ') are not valid for identifiers like aliases. In SQL, single quotes always represent string literals, not names.
For example, if you write:
SELECT CountryOfOrigin,
COUNT(1) AS 'Painting Count'
FROM Gallery.Paintings
GROUP BY CountryOfOrigin;Some tools will still display the output column heading as Painting Count, but what’s really happening is the database is treating 'Painting Count' as a literal string. It looks like it worked, but technically you just selected a constant string and renamed the count column with no real alias.
This can cause errors in more complex queries or when porting code between platforms, and should be avoided.
Using Multiple Aggregations
You can use more than one aggregation at a time, and aliases make the output much easier to understand.
Example: Works per artist with earliest and latest year
SELECT Artist,
COUNT(1) AS PaintingCount,
MIN(YearCreated) AS EarliestWork,
MAX(YearCreated) AS LatestWork
FROM Gallery.Paintings
GROUP BY Artist
ORDER BY PaintingCount DESC,
Artist ASC;Result:
| Artist | PaintingCount | EarliestWork | LatestWork |
|---|---|---|---|
| Edward Hopper | 3 | 1927 | 1942 |
| Edvard Munch | 3 | 1885 | 1899 |
| Johannes Vermeer | 3 | 1658 | 1665 |
| Katsushika Hokusai | 3 | 1830 | 1831 |
| Salvador Dalí | 3 | 1931 | 1937 |
| Vincent van Gogh | 3 | 1888 | 1889 |
| Leonardo da Vinci | 2 | 1498 | 1503 |
Adding More Columns
Sometimes you’ll want to group multiple columns to provide more context in results.
Keep in mind, if a column appears in the SELECT list and isn’t aggregated, it must be listed in GROUP BY. This ensures that the database knows exactly how to group rows before calculating the summary values.
Example: Paintings by country and artist
SELECT CountryOfOrigin,
Artist,
COUNT(1) AS PaintingCount
FROM Gallery.Paintings
GROUP BY CountryOfOrigin,
Artist
ORDER BY CountryOfOrigin ASC,
PaintingCount DESC,
Artist ASC;Result:
| CountryOfOrigin | Artist | PaintingCount |
|---|---|---|
| Italy | Leonardo da Vinci | 2 |
| Japan | Katsushika Hokusai | 3 |
| Netherlands | Johannes Vermeer | 3 |
| Netherlands | Vincent van Gogh | 3 |
| Norway | Edvard Munch | 3 |
| Spain | Salvador Dalí | 3 |
| United States | Edward Hopper | 3 |
Filtering Groups with HAVING
You already know WHERE filters rows before grouping. But what if you want to filter after the grouping? That’s where HAVING comes in.
WHEREfilters rows before grouping.HAVINGfilters groups after aggregation.
Example: Only countries with more than 3 paintings
SELECT CountryOfOrigin,
COUNT(1) AS PaintingCount
FROM Gallery.Paintings
GROUP BY CountryOfOrigin
HAVING COUNT(1) > 3
ORDER BY PaintingCount DESC,
CountryOfOrigin;Result:
| CountryOfOrigin | PaintingCount |
|---|---|
| Netherlands | 6 |
Key Takeaways
- Use aggregate functions (
COUNT,SUM,AVG,MIN,MAX) to summarize data. GROUP BYforms groups; aggregates compute summaries per group.- Every selected column must be aggregated or listed in
GROUP BY. - Aliases (
AS) make results readable and maintainable. WHEREfilters rows before grouping;HAVINGfilters groups after aggregation.
Once you’re comfortable grouping rows by columns, you’ll be ready to explore more powerful options. In the next post, we’ll look at more ways to group data by building multiple levels of summaries in a single query.

