All posts
SQLAugust 28, 20267 min read

SQL GROUP BY Explained for Beginners (Examples)

Confused by SQL GROUP BY? This beginner guide explains grouping, COUNT, SUM, and HAVING with simple, copy-paste SQL examples you can run today.

F
Fepiq Team
Fepiq

If you searched for "SQL GROUP BY explained for beginners," here is the short answer: GROUP BY takes many rows that share the same value in a column and squeezes them into one summary row per value, so you can count, add up, or average them. This guide builds that idea from scratch with tiny, copy-pasteable examples, so you do not need any prior SQL knowledge beyond a basic SELECT statement.

By the end, you will be able to answer questions like "how many books do I have in each genre?" or "what is the average price per genre?" using nothing but a single SQL query.

What does GROUP BY actually do?

Imagine you dump a big pile of receipts on a table and someone asks, "how much did we spend per store?" You would naturally sort the receipts into small piles, one pile per store, then add up each pile. GROUP BY does exactly that inside a database: it sorts rows into buckets based on a column you choose, and then lets you run a calculation on each bucket separately.

A calculation that works on a whole bucket of rows (instead of one row at a time) is called an aggregate function. The most common ones are COUNT, SUM, AVG, MIN, and MAX. GROUP BY and aggregate functions almost always appear together.

Setting up a sample table

To follow along, create a small table called books and add a few rows. You can run this in PostgreSQL, MySQL, or any SQL tool you like — the GROUP BY syntax shown here works the same way in all of them.

sql
CREATE TABLE books (
    id SERIAL PRIMARY KEY,
    title VARCHAR(100),
    genre VARCHAR(50),
    format VARCHAR(20),
    price NUMERIC(6,2)
);

INSERT INTO books (title, genre, format, price) VALUES
('The Hobbit', 'Fantasy', 'Paperback', 15.99),
('Dune', 'Sci-Fi', 'Hardcover', 12.50),
('1984', 'Sci-Fi', 'Paperback', 9.99),
('The Shining', 'Horror', 'Paperback', 11.00),
('It', 'Horror', 'Hardcover', 14.25),
('Foundation', 'Sci-Fi', 'Paperback', 13.75);

Line by line: CREATE TABLE books defines a new table with five columns. id SERIAL PRIMARY KEY makes an auto-incrementing unique number for each row. title, genre, and format store text (VARCHAR just means "text with a maximum length"). price uses NUMERIC(6,2), which means up to 6 digits total with 2 of them after the decimal point — good for money. The INSERT INTO statement then adds six rows of sample data, one book per line, so we have something real to group.

A simple GROUP BY example

Let's answer: "how many books do I have in each genre?"

sql
SELECT genre, COUNT(*) AS total_books
FROM books
GROUP BY genre;

SELECT genre, COUNT(*) AS total_books tells SQL to show the genre column plus a count, and to label that count column total_books (AS just renames a column in the results). FROM books says which table to read. GROUP BY genre is the key line: it collects all rows that share the same genre value into one bucket before COUNT(*) runs, so COUNT(*) counts rows per bucket instead of counting the whole table. The result has one row per genre — for example Sci-Fi with 3, Horror with 2, and Fantasy with 1 — instead of six rows for six books.

Using SUM and AVG to do math per group

COUNT is not the only aggregate function. You can also total up or average a numeric column per group.

sql
SELECT genre,
       SUM(price) AS total_price,
       AVG(price) AS avg_price
FROM books
GROUP BY genre;

SUM(price) adds up the price column within each genre bucket, and AVG(price) divides that total by how many rows are in the bucket to get an average. Because both SUM and AVG appear in the same SELECT as genre, and genre is also listed in GROUP BY, this query is valid — every column in SELECT is either grouped or wrapped in an aggregate function.

FunctionWhat it calculatesExample
COUNT(*)Number of rows in each groupCOUNT(*) AS total_books
SUM(column)Total of a numeric column in each groupSUM(price) AS total_price
AVG(column)Average of a numeric column in each groupAVG(price) AS avg_price
MIN(column)Smallest value in each groupMIN(price) AS cheapest
MAX(column)Largest value in each groupMAX(price) AS most_expensive

Filtering groups with HAVING

WHERE filters individual rows before grouping happens, so it cannot check an aggregate result like COUNT(*) — the count does not exist yet at that point in the query. To filter on an aggregate value, SQL gives you a separate keyword: HAVING, which runs after the groups are built.

sql
SELECT genre, COUNT(*) AS total_books
FROM books
GROUP BY genre
HAVING COUNT(*) > 1;

This query groups books by genre, counts each group, and then HAVING COUNT(*) > 1 throws away any genre that has only one book. In our sample data, that removes Fantasy (only "The Hobbit") and keeps Sci-Fi and Horror. Think of it this way: WHERE removes rows before grouping, HAVING removes whole groups after grouping.

Grouping by more than one column

You are not limited to one column. Grouping by two columns creates one bucket per unique combination of both values.

sql
SELECT genre, format, COUNT(*) AS total_books
FROM books
GROUP BY genre, format;

GROUP BY genre, format now creates a separate bucket for every genre-and-format pair — Sci-Fi Paperback is a different bucket from Sci-Fi Hardcover. This is useful when a single column is too broad and you need a finer breakdown, for example "books per genre, split by paperback versus hardcover."

Common mistakes beginners make with GROUP BY

  • Selecting a plain column that is not in GROUP BY and not wrapped in an aggregate function — PostgreSQL will reject the query with an error, because it does not know which single value to show for that column in a group of many rows.
  • Using WHERE to filter on an aggregate result, like WHERE COUNT(*) > 1, instead of HAVING. WHERE runs before grouping, so it cannot see the count.
  • Forgetting that COUNT(column) skips NULL values, while COUNT(*) counts every row regardless of NULLs — the two can return different numbers.
  • Grouping by too many columns, which can create one tiny group per row and defeats the purpose of summarizing data.
  • Assuming GROUP BY sorts the results. It only creates groups; add ORDER BY if you want the output in a specific order.

If you are not yet comfortable with basic filtering, it helps to revisit SELECT and WHERE first, since GROUP BY builds directly on top of those two clauses.

Not sure how SELECT and WHERE work yet? Start with our beginner guide before tackling GROUP BY.

Read: SQL SELECT and WHERE Explained for Beginners

Frequently asked questions

What is the difference between WHERE and HAVING in SQL?+

WHERE filters individual rows before grouping happens, and it cannot reference aggregate functions like COUNT() or SUM(). HAVING filters entire groups after GROUP BY has combined the rows, so it can check aggregate results. Use WHERE to remove rows early, and HAVING to remove whole groups afterward.

Can I use GROUP BY without an aggregate function?+

Yes, though it is unusual. On its own, GROUP BY just removes duplicate combinations of the grouped columns, similar to SELECT DISTINCT. Its real power shows up when you pair it with COUNT, SUM, AVG, MIN, or MAX.

Why do I get an error about a column not being in GROUP BY?+

Databases like PostgreSQL require every column in SELECT to be either listed in GROUP BY or wrapped in an aggregate function. Each result row now represents a whole group of original rows, so SQL needs a clear rule for reducing any non-grouped column down to one value.

Does the order of the columns in GROUP BY matter?+

For the grouping result itself, no — GROUP BY genre, format produces the same groups as GROUP BY format, genre. Column order mainly affects readability, though in some databases it can influence how indexes are used to speed up the query.

Can I sort the results of a GROUP BY query?+

Yes. Add an ORDER BY clause after GROUP BY, and after HAVING if you use one, to sort the grouped results. For example, ORDER BY total_books DESC would list the biggest groups first.

GROUP BY feels tricky the first time, but it comes down to one idea: put similar rows in the same bucket, then run a calculation on each bucket. Practice with your own small table, try COUNT and SUM first, and add HAVING once grouping itself feels natural.

Want help turning raw data into clear reports for your business?

Talk to Fepiq

Get new posts in your inbox

Occasional, no-fluff notes on shipping modern software — startups, automation, Laravel, Shopify and more. No spam, unsubscribe anytime.

Keep reading

Related posts

All posts

Let's build something

Ready to ship your next product with Fepiq?

Book a free discovery call. We'll listen, ask sharp questions, and send you a proposal within 3 business days.