How to Write SQL Query Examples for a Revenue Report

A revenue report is the most common SQL query you'll ever write: join two tables, aggregate a metric, rank by it, take the top N. This project builds a top-5-customers-by-spend report with SELECT customer_name, COUNT(*), SUM(amount) - the exact GROUP BY / ORDER BY DESC / LIMIT 5 pattern behind almost every business dashboard.

Data Engineersqlpostgresqlaggregation

The pattern behind almost every SQL report

Business reporting queries look intimidating, but the vast majority are one shape: pick a dimension (customer, product, region), aggregate a metric (revenue, count, average), rank by that metric, and keep the top few rows. Once you can write this one query, most dashboard queries are just variations with extra filters.

For this project the sales team wants the top 5 customers by total spend, with three columns: customer_name, order_count, and total_spend. The data lives in two tables - a customers table and an orders table linked by customer_id:

-- customers: id, name
-- orders:    id, customer_id, amount, created_at

Spend is not stored anywhere; it has to be computed by summing each customer's order amounts. That is what makes this an aggregation query rather than a plain SELECT.

Step 1 - join customers to their orders

A customer's name lives in one table and their orders in another, so start with an inner JOIN on the foreign key. This produces one row per order, each carrying the customer's name:

SELECT c.name, o.amount
FROM customers c
JOIN orders o ON o.customer_id = c.id;

Step 2 - group and aggregate

To collapse those per-order rows into one row per customer, GROUP BY the customer, then apply aggregate functions. COUNT(o.id) counts their orders and SUM(o.amount) adds up their spend. Every non-aggregated column in the SELECT must appear in the GROUP BY, so group by both c.id and c.name:

SELECT
    c.name        AS customer_name,
    COUNT(o.id)   AS order_count,
    SUM(o.amount) AS total_spend
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name;

Grouping by c.id (the primary key) is important - two customers could share a name, and grouping by the id keeps them as separate rows.

Step 3 - rank and limit

Sort by the aggregated total descending so the biggest spenders come first, then cut the result to five rows with LIMIT. This is the complete report:

SELECT
    c.name        AS customer_name,
    COUNT(o.id)   AS order_count,
    SUM(o.amount) AS total_spend
FROM customers c
JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY total_spend DESC
LIMIT 5;

You can order by the total_spend alias directly - PostgreSQL resolves ORDER BY aliases after the SELECT list is computed. Save it as report.sql and run it:

psql -h localhost -U postgres -d app -f report.sql

Against the seeded data the top row is Alice Chen with 3 orders totalling 950.00, followed by the next four customers by spend.

Common mistakes

Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.

Try it in a workspace →

What you'll practice

FAQ

How do I get the top 5 customers by total spend in SQL?

Join customers to orders on customer_id, GROUP BY the customer, and use SUM(amount) to total each customer's spend. Then ORDER BY that sum DESC and LIMIT 5 to keep only the highest spenders.

How do I sum a column per group in SQL?

Use SUM() with a GROUP BY clause. SELECT customer_id, SUM(amount) FROM orders GROUP BY customer_id returns one row per customer with their total. Every non-aggregated SELECT column must also appear in the GROUP BY.

What is the difference between WHERE and HAVING in SQL?

WHERE filters individual rows before they are grouped, and cannot reference aggregate functions. HAVING filters the grouped results, so it is where you put conditions on COUNT, SUM, or AVG - for example HAVING SUM(amount) > 500.

Can I use a column alias in ORDER BY?

Yes. In PostgreSQL you can ORDER BY an alias defined in the SELECT list, such as ORDER BY total_spend DESC, because ORDER BY is evaluated after the select list. You cannot use that alias in WHERE or GROUP BY, however.

Keep learning

Rank Rows Per Group With SQL Window FunctionsData projectSpeed Up a Slow SQL Report QueryData projectAdd an Index to a Slow Postgres QueryData projectData roadmapStep by step to hiredData interview questionsSTAR answersAll Data projectsProjects hub

Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.

Open the workspace →