GROUP BY groups rows with the same values in specified columns into summary rows, typically used with aggregate functions (COUNT, SUM, AVG, MAX, MIN). HAVING filters grouped results based on aggregate conditions, similar to how WHERE filters individual rows.
Syntax
SELECT
column1,
column2,
aggregate_function(column3)FROMtableWHEREcondition-- Filters before groupingGROUPBY column1, column2
HAVING aggregate_condition -- Filters after groupingORDERBY
SELECT
column1,
column2,
aggregate_function(column3)FROMtableWHEREcondition-- Filters before groupingGROUPBY column1, column2
HAVING aggregate_condition -- Filters after groupingORDERBY
SELECT
column1,
column2,
aggregate_function(column3)FROMtableWHEREcondition-- Filters before groupingGROUPBY column1, column2
HAVING aggregate_condition -- Filters after groupingORDERBY
Platform-Specific Notes
Snowflake:
Supports GROUP BY ALL (groups by all non-aggregated columns)
SELECT
department,
job_title,COUNT(*)as employee_count,
AVG(salary)as avg_salary,
MIN(salary)as min_salary,
MAX(salary)as max_salary,
SUM(salary)as total_payroll
FROM employees
GROUPBY department, job_title
HAVINGCOUNT(*) > 1-- Only show roles with multiple employeesORDERBY department, avg_salary DESC
SELECT
department,
job_title,COUNT(*)as employee_count,
AVG(salary)as avg_salary,
MIN(salary)as min_salary,
MAX(salary)as max_salary,
SUM(salary)as total_payroll
FROM employees
GROUPBY department, job_title
HAVINGCOUNT(*) > 1-- Only show roles with multiple employeesORDERBY department, avg_salary DESC
SELECT
department,
job_title,COUNT(*)as employee_count,
AVG(salary)as avg_salary,
MIN(salary)as min_salary,
MAX(salary)as max_salary,
SUM(salary)as total_payroll
FROM employees
GROUPBY department, job_title
HAVINGCOUNT(*) > 1-- Only show roles with multiple employeesORDERBY department, avg_salary DESC
Sample Data (employees table):
employee_id
name
department
job_title
salary
1
Alice
Sales
Sales Manager
85000
2
Bob
Sales
Sales Rep
55000
3
Carol
Sales
Sales Rep
58000
4
David
Engineering
Software Engineer
95000
5
Emma
Engineering
Software Engineer
92000
6
Frank
Engineering
Software Engineer
98000
7
Grace
Engineering
QA Engineer
72000
8
Henry
Engineering
QA Engineer
70000
Result:
department
job_title
employee_count
avg_salary
min_salary
max_salary
total_payroll
Engineering
Software Engineer
3
95000.00
92000
98000
285000
Engineering
QA Engineer
2
71000.00
70000
72000
142000
Sales
Sales Rep
2
56500.00
55000
58000
113000
Note: Sales Manager excluded (only 1 employee)
Example 4: WHERE vs HAVING
All Platforms:
SELECT
product_category,
EXTRACT(YEARFROM sale_date)as sale_year,COUNT(*)as transaction_count,
SUM(quantity)as units_sold,
SUM(revenue)as total_revenue,
AVG(revenue)as avg_transaction_value
FROM sales
WHERE
sale_date >= '2023-01-01'-- WHERE: Filter rows before groupingAND product_category != 'Discontinued'GROUPBY
product_category,
EXTRACT(YEARFROM sale_date)HAVING
SUM(revenue) > 10000-- HAVING: Filter groups after aggregationANDCOUNT(*) >= 50ORDERBY
sale_year DESC,
total_revenue DESC
SELECT
product_category,
EXTRACT(YEARFROM sale_date)as sale_year,COUNT(*)as transaction_count,
SUM(quantity)as units_sold,
SUM(revenue)as total_revenue,
AVG(revenue)as avg_transaction_value
FROM sales
WHERE
sale_date >= '2023-01-01'-- WHERE: Filter rows before groupingAND product_category != 'Discontinued'GROUPBY
product_category,
EXTRACT(YEARFROM sale_date)HAVING
SUM(revenue) > 10000-- HAVING: Filter groups after aggregationANDCOUNT(*) >= 50ORDERBY
sale_year DESC,
total_revenue DESC
SELECT
product_category,
EXTRACT(YEARFROM sale_date)as sale_year,COUNT(*)as transaction_count,
SUM(quantity)as units_sold,
SUM(revenue)as total_revenue,
AVG(revenue)as avg_transaction_value
FROM sales
WHERE
sale_date >= '2023-01-01'-- WHERE: Filter rows before groupingAND product_category != 'Discontinued'GROUPBY
product_category,
EXTRACT(YEARFROM sale_date)HAVING
SUM(revenue) > 10000-- HAVING: Filter groups after aggregationANDCOUNT(*) >= 50ORDERBY
sale_year DESC,
total_revenue DESC
Sample Result:
product_category
sale_year
transaction_count
units_sold
total_revenue
avg_transaction_value
Electronics
2024
1250
3500
125000.00
100.00
Furniture
2024
680
890
45000.00
66.18
Electronics
2023
950
2800
98000.00
103.16
Example 5: GROUPING SETS, CUBE, and ROLLUP
All Platforms:
-- GROUPING SETS: Specify exact grouping combinationsSELECT
region,
product_category,
SUM(revenue)as total_revenue
FROM sales
GROUPBYGROUPINGSETS((region, product_category),-- By region and category(region),-- By region only(product_category),-- By category only()-- Grand total)ORDERBY region NULLS LAST, product_category NULLS LAST;
-- CUBE: All possible combinationsSELECT
region,
product_category,
SUM(revenue)as total_revenue
FROM sales
GROUPBYCUBE(region, product_category);
-- ROLLUP: Hierarchical aggregationsSELECTyear,
quarter,month,
SUM(revenue)as total_revenue
FROM sales_data
GROUPBYROLLUP(year, quarter,month)ORDERBYyear, quarter,month
-- GROUPING SETS: Specify exact grouping combinationsSELECT
region,
product_category,
SUM(revenue)as total_revenue
FROM sales
GROUPBYGROUPINGSETS((region, product_category),-- By region and category(region),-- By region only(product_category),-- By category only()-- Grand total)ORDERBY region NULLS LAST, product_category NULLS LAST;
-- CUBE: All possible combinationsSELECT
region,
product_category,
SUM(revenue)as total_revenue
FROM sales
GROUPBYCUBE(region, product_category);
-- ROLLUP: Hierarchical aggregationsSELECTyear,
quarter,month,
SUM(revenue)as total_revenue
FROM sales_data
GROUPBYROLLUP(year, quarter,month)ORDERBYyear, quarter,month
-- GROUPING SETS: Specify exact grouping combinationsSELECT
region,
product_category,
SUM(revenue)as total_revenue
FROM sales
GROUPBYGROUPINGSETS((region, product_category),-- By region and category(region),-- By region only(product_category),-- By category only()-- Grand total)ORDERBY region NULLS LAST, product_category NULLS LAST;
-- CUBE: All possible combinationsSELECT
region,
product_category,
SUM(revenue)as total_revenue
FROM sales
GROUPBYCUBE(region, product_category);
-- ROLLUP: Hierarchical aggregationsSELECTyear,
quarter,month,
SUM(revenue)as total_revenue
FROM sales_data
GROUPBYROLLUP(year, quarter,month)ORDERBYyear, quarter,month
Sample Data (sales table):
region
product_category
revenue
East
Electronics
50000
East
Furniture
30000
West
Electronics
60000
West
Furniture
25000
Result (GROUPING SETS):
region
product_category
total_revenue
East
Electronics
50000
East
Furniture
30000
West
Electronics
60000
West
Furniture
25000
East
NULL
80000
West
NULL
85000
NULL
Electronics
110000
NULL
Furniture
55000
NULL
NULL
165000
Example 6: GROUP BY with CASE Statements
All Platforms:
SELECTCASEWHEN age < 25THEN'18-24'WHEN age < 35THEN'25-34'WHEN age < 50THEN'35-49'ELSE'50+'ENDas age_group,COUNT(*)as customer_count,
AVG(lifetime_value)as avg_lifetime_value,
SUM(total_purchases)as total_purchases
FROM customers
GROUPBYCASEWHEN age < 25THEN'18-24'WHEN age < 35THEN'25-34'WHEN age < 50THEN'35-49'ELSE'50+'ENDHAVINGCOUNT(*) >= 10ORDERBY
SELECTCASEWHEN age < 25THEN'18-24'WHEN age < 35THEN'25-34'WHEN age < 50THEN'35-49'ELSE'50+'ENDas age_group,COUNT(*)as customer_count,
AVG(lifetime_value)as avg_lifetime_value,
SUM(total_purchases)as total_purchases
FROM customers
GROUPBYCASEWHEN age < 25THEN'18-24'WHEN age < 35THEN'25-34'WHEN age < 50THEN'35-49'ELSE'50+'ENDHAVINGCOUNT(*) >= 10ORDERBY
SELECTCASEWHEN age < 25THEN'18-24'WHEN age < 35THEN'25-34'WHEN age < 50THEN'35-49'ELSE'50+'ENDas age_group,COUNT(*)as customer_count,
AVG(lifetime_value)as avg_lifetime_value,
SUM(total_purchases)as total_purchases
FROM customers
GROUPBYCASEWHEN age < 25THEN'18-24'WHEN age < 35THEN'25-34'WHEN age < 50THEN'35-49'ELSE'50+'ENDHAVINGCOUNT(*) >= 10ORDERBY
Sample Result:
age_group
customer_count
avg_lifetime_value
total_purchases
18-24
245
385.50
2450
25-34
580
720.25
6800
35-49
450
950.75
5200
50+
125
1150.00
1580
WHERE vs HAVING Comparison
Aspect
WHERE
HAVING
Filters
Individual rows
Grouped results
Applied
Before GROUP BY
After GROUP BY
Can use aggregates
No
Yes
Use with
Any query
Only with GROUP BY
Example
WHERE price > 100
HAVING SUM(price) > 1000
Common Aggregate Functions
Function
Description
Example
COUNT(*)
Count all rows
COUNT(*)
COUNT(column)
Count non-NULL values
COUNT(email)
SUM(column)
Sum of values
SUM(revenue)
AVG(column)
Average of values
AVG(price)
MIN(column)
Minimum value
MIN(order_date)
MAX(column)
Maximum value
MAX(salary)
COUNT(DISTINCT column)
Count unique values
COUNT(DISTINCT customer_id)
Common Use Cases
Sales reports: Revenue by product, region, time period
*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.
*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.
*dbt® and dbt Core® are federally registered trademarks of dbt Labs, Inc. in the United States and various jurisdictions around the world. Paradime is not a partner of dbt Labs. All rights therein are reserved to dbt Labs. Paradime is not a product or service of or endorsed by dbt Labs, Inc.