What Are Aggregate Functions?
Every day, an ecommerce business needs answers like "How many orders came in today?" or "What is the average order value this month?" Aggregate functions collapse many rows into a single summary value. They are the foundation of any reporting query you will write.
COUNT
COUNT tells you how many rows match your criteria.
-- How many customers do we have?
SELECT COUNT(*) AS total_customers
FROM customers c;
-- How many customers have a phone number on file?
SELECT COUNT(c.phone) AS customers_with_phone
FROM customers c;
COUNT(*) counts every row. COUNT(column) skips NULL values in that column.
COUNT(*) when you want total rows. Use COUNT(column) when you want to know how many rows have a value in a specific column.SUM
SUM adds up numeric values. Use it for totals like revenue, quantities, or discounts.
-- Total revenue from all completed orders
SELECT SUM(o.total_amount) AS total_revenue
FROM orders o
WHERE o.status = 'delivered';
-- Total quantity sold per product
SELECT oi.product_id,
SUM(oi.quantity) AS total_units_sold
FROM order_items oi
GROUP BY oi.product_id;
AVG
AVG returns the mean of a numeric column, ignoring NULL values.
-- Average order value
SELECT AVG(o.total_amount) AS avg_order_value
FROM orders o;
-- Average unit price per category
SELECT p.category_id,
AVG(p.price) AS avg_price
FROM products p
GROUP BY p.category_id;
MIN and MAX
MIN and MAX return the smallest and largest values respectively. They work on numbers, dates, and text.
-- Earliest and most recent order dates
SELECT MIN(o.order_date) AS first_order,
MAX(o.order_date) AS latest_order
FROM orders o;
-- Cheapest and most expensive product per category
SELECT p.category_id,
MIN(p.price) AS cheapest,
MAX(p.price) AS most_expensive
FROM products p
GROUP BY p.category_id;
Combining Aggregates
You can use multiple aggregate functions in the same SELECT. This is how most summary reports are built.
-- Order summary stats
SELECT COUNT(*) AS total_orders,
SUM(o.total_amount) AS revenue,
AVG(o.total_amount) AS avg_order_value,
MIN(o.total_amount) AS smallest_order,
MAX(o.total_amount) AS largest_order
FROM orders o
WHERE o.status = 'delivered';
Common Mistakes
-- wrong
SELECT COUNT(*)
FROM orders o
WHERE o.status = 'delivered'
HAVING COUNT(*) > 100;
HAVING filters grouped results. Without GROUP BY, this works on the single aggregate row, which is usually not what you intend.
-- correct
SELECT o.status,
COUNT(*) AS total
FROM orders o
GROUP BY o.status
HAVING COUNT(*) > 100;
-- wrong
SELECT AVG(oi.discount_pct) AS avg_discount
FROM order_items oi
WHERE oi.discount_pct != NULL;
You cannot compare against NULL with !=. Use IS NOT NULL instead.
-- correct
SELECT AVG(oi.discount_pct) AS avg_discount
FROM order_items oi
WHERE oi.discount_pct IS NOT NULL;
-- wrong
SELECT p.category_id, p.product_name, COUNT(*)
FROM products p
GROUP BY p.category_id;
Every non-aggregate column in SELECT must appear in GROUP BY. p.product_name is missing here.
-- correct
SELECT p.category_id, COUNT(*) AS product_count
FROM products p
GROUP BY p.category_id;
Quick Recap
COUNT(*)counts all rows;COUNT(column)skips NULLs in that columnSUM,AVG,MIN, andMAXall ignore NULL values automatically- You can mix multiple aggregate functions in a single query
- Every non-aggregate column in
SELECTmust also appear inGROUP BY - Use
HAVINGto filter on aggregate results, notWHERE
SELECT COUNT(*) AS total_products, SUM(CASE WHEN p.is_active = 1 THEN 1 ELSE 0 END) AS active_products FROM products p;
SELECT pay.method, SUM(pay.amount) AS total_collected, AVG(pay.amount) AS avg_payment FROM payments pay WHERE pay.status = 'completed' GROUP BY pay.method;
SELECT cat.category_name, SUM(oi.line_total) AS total_revenue FROM order_items oi JOIN products p ON oi.product_id = p.product_id JOIN categories cat ON p.category_id = cat.category_id GROUP BY cat.category_name HAVING SUM(oi.line_total) > 5000 ORDER BY total_revenue DESC;
Aggregate functions compute a single result from a set of rows. Examples include COUNT, SUM, AVG, MIN, and MAX.
COUNT(*) counts all rows including NULLs. COUNT(column) counts only rows where that column is not NULL.
Yes. Without GROUP BY, the aggregate runs across the entire result set and returns one row.
SQL aggregate functions skip NULLs by default, so AVG divides only by the count of non-NULL values.