GROUP BY is the SQL clause that collapses rows sharing the same values into groups, so aggregate functions like COUNT and SUM can compute one result per group. It’s the clause behind almost every business question that starts with “per”: revenue per region, signups per week, orders per customer.
How it works
GROUP BY takes one or more columns and partitions the table by their values — every distinct combination becomes one group, and one output row. Aggregates in the SELECT list are then computed within each group:
SELECT
region,
product_category,
COUNT(*) AS orders,
SUM(amount) AS revenue
FROM orders
GROUP BY region, product_category;
This returns one row per region-and-category pair, with that pair’s order count and revenue. In analytics vocabulary, the grouped columns are your dimensions and the aggregates are your measures — GROUP BY is where that split becomes literal SQL.
The rule that trips everyone up
Once a query has a GROUP BY, every column in the SELECT list must either appear in the GROUP BY or sit inside an aggregate function. The database has no way to pick which customer_name to show for a group of fifty rows unless you tell it how — group by it, or aggregate it. PostgreSQL, SQL Server, and most others enforce this strictly; MySQL historically let ambiguous columns through and picked an arbitrary value, a leniency that produced years of subtly wrong reports before strict mode became the default. If a grouped query errors with something like “column must appear in the GROUP BY clause,” this rule is what it’s enforcing.
Two adjacent clauses complete the picture. WHERE filters rows before grouping; HAVING filters the groups afterward, which is how you express conditions on the aggregates themselves. And note the difference from a window function, which computes group-level values while keeping every row — GROUP BY collapses, OVER doesn’t.
GROUP BY in Metabase
If you use Metabase’s query builder, you already use GROUP BY constantly without typing it: the Summarize step is a GROUP BY. Picking an aggregation (“Sum of Amount”) and a grouping (“by Region, by Created At: Month”) generates exactly the SQL above against your database — you can confirm by clicking View SQL on a query builder question, which is also a decent way to learn the clause. Materializing a frequently-grouped result as a summary table or a materialized view upstream is the usual next step when those grouped queries get heavy enough to slow dashboards down.
Related terms
Further reading
Put it to work
- SQL cheat sheet — Cheat sheet
- Best practices for writing SQL queries — Tutorial
- Data warehouse dashboard — Dashboard