All 40 lessons
Lesson 13 of 40 · Aggregation
SQL Aggregate Functions
By Skillancy Editorial Team
Every example runs on the ShopKart dataset, already loaded in the free SQL Compiler. Click Try it in the SQL Compiler under a query to open it there, then press Run.
Aggregate functions turn many rows into one number: a count, a total, an average, a minimum or a maximum. They're how you go from raw orders to metrics like revenue, average order value and best-selling price point.
The five core aggregates
| Function | Returns | Ignores NULLs? |
|---|---|---|
COUNT(*) | number of rows | counts every row |
COUNT(col) | number of non-NULL values | yes |
SUM(col) | total | yes |
AVG(col) | mean | yes |
MIN(col) / MAX(col) | smallest / largest | yes |
Syntax
SELECT AGGREGATE_FUNCTION(column)
FROM table_name
WHERE condition;Examples
1. COUNT rows
How many products does ShopKart sell?
SELECT COUNT(*) AS product_count
FROM products;Try it in the SQL CompilerResult:
| product_count |
|---|
| 15 |
(1 row)
2. COUNT(*) vs COUNT(column) vs COUNT(DISTINCT)
SELECT COUNT(*) AS all_orders,
COUNT(delivery_date) AS orders_with_delivery,
COUNT(DISTINCT customer_id) AS unique_customers
FROM orders;Try it in the SQL CompilerResult:
| all_orders | orders_with_delivery | unique_customers |
|---|---|---|
| 24 | 18 | 11 |
(1 row)
Three different numbers from the same table. Pick the one that matches the question.
3. SUM: total revenue
Net revenue across all order lines, after discounts:
SELECT ROUND(SUM(quantity * unit_price * (1 - discount_pct / 100)), 2) AS net_revenue,
SUM(quantity) AS units_sold
FROM order_items;Try it in the SQL CompilerResult:
| net_revenue | units_sold |
|---|---|
| 196531.90 | 59 |
(1 row)
4. AVG, MIN and MAX
SELECT ROUND(AVG(price), 2) AS avg_price,
MIN(price) AS cheapest,
MAX(price) AS most_expensive
FROM products;Try it in the SQL CompilerResult:
| avg_price | cheapest | most_expensive |
|---|---|---|
| 5464.87 | 199.00 | 31990.00 |
(1 row)
MIN and MAX also work on dates and text:
SELECT MIN(order_date) AS first_order,
MAX(order_date) AS latest_order
FROM orders;Try it in the SQL CompilerResult:
| first_order | latest_order |
|---|---|
| 2025-01-05 | 2026-02-10 |
(1 row)
5. Aggregates with WHERE
WHERE filters rows before they're aggregated. Average delivery time for delivered orders:
SELECT COUNT(*) AS delivered_orders,
ROUND(AVG(delivery_date - order_date), 1) AS avg_days_to_deliver
FROM orders
WHERE status = 'Delivered';Try it in the SQL CompilerResult:
| delivered_orders | avg_days_to_deliver |
|---|---|
| 16 | 3.6 |
(1 row)
6. Several conditions in one pass with FILTER
PostgreSQL's FILTER clause applies a condition to a single aggregate:
SELECT COUNT(*) AS total,
COUNT(*) FILTER (WHERE payment_method = 'UPI') AS upi_orders,
COUNT(*) FILTER (WHERE payment_method = 'COD') AS cod_orders
FROM orders;Try it in the SQL CompilerResult:
| total | upi_orders | cod_orders |
|---|---|---|
| 24 | 11 | 4 |
(1 row)
In other databases, write SUM(CASE WHEN payment_method = 'UPI' THEN 1 ELSE 0 END) instead (see CASE WHEN).
Common mistakes
Mixing aggregated and non-aggregated columns. The database can't show one product_name next to a single MAX(price):
SELECT product_name, MAX(price)
FROM products;Try it in the SQL CompilerError: column "products.product_name" must appear in the GROUP BY clause or be used in an aggregate function
To find which product is most expensive, sort and limit, or use GROUP BY and subqueries.
- Averaging an average. The average of monthly averages is not the overall average if months have different order counts.
- Forgetting NULL handling.
AVG(col)skips NULLs instead of treating them as 0.
Practice question
How many order lines had a discount, and what was the average discount on those lines?
Show solution
SELECT COUNT(*) AS discounted_lines,
ROUND(AVG(discount_pct), 2) AS avg_discount_pct
FROM order_items
WHERE discount_pct > 0;Try it in the SQL CompilerResult:
| discounted_lines | avg_discount_pct |
|---|---|
| 12 | 9.58 |
(1 row)
Key takeaways
- Aggregates collapse many rows into one value.
COUNT(*)counts rows;COUNT(col)skips NULLs;COUNT(DISTINCT col)counts unique values.- Aggregates ignore NULLs, and return NULL when no rows match.
- You can't mix aggregated and plain columns without
GROUP BY.
Frequently asked questions
What is the difference between COUNT(*) and COUNT(column)?
COUNT(*) counts all rows. COUNT(column) counts only rows where that column is not NULL. COUNT(DISTINCT column) counts unique non-NULL values.
Can I use an aggregate function in WHERE?
No. WHERE runs before aggregation. To filter on an aggregate such as SUM or COUNT, use HAVING with GROUP BY, or put the aggregate in a subquery.
Why does SUM return NULL instead of 0?
When no rows match, SUM, AVG, MIN and MAX return NULL. Wrap them in COALESCE(SUM(x), 0) if you need a zero.
Keep learning SQL
One email a week with SQL and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Prefer a one-page summary? Download the free SQL cheat sheet (PDF)