Scenario: Brightlane's finance team needs a monthly revenue summary to track order activity over time.
Task: Write a query to return each calendar month, the number of orders placed in that month, and the total orders revenue for that month.
Assumptions:
- The
orderstable holds one row per placed order, with the placement timestamp stored inordered_atand the order amount stored intotal_amount. - A calendar month is identified by its first day and covers every order placed within that month.
Output:
- One row per calendar month present in the data.
- Columns in this order:
month(the first day of the calendar month),order_count,revenue.
Schema · ecommerce 5 tables
Run previews · Check grades
Write a query, then run it to see results here.
Worked solution Try it yourself first
SELECT
DATE_TRUNC('month', ordered_at)::date AS MONTH,
COUNT(*) AS order_count,
SUM(total_amount) AS revenue
FROM
orders
GROUP BY
DATE_TRUNC('month', ordered_at) The shape
date_trunc('month', ordered_at) collapses every order placement timestamp down to the first day of its month, which makes all orders within the same month share a single grouping key. The aggregate then runs once per month, returning one row per calendar month with the count and revenue rolled up across every order in it.
Clause by clause
SELECT date_trunc('month', ordered_at)::date AS month, COUNT(*) AS order_count, SUM(total_amount) AS revenuereturns one row per month. The::datecast strips the time component off the truncated value so the column reads as a calendar date instead of a midnight timestamp.COUNT(*)counts every order placed in the month, andSUM(total_amount)adds up the dollar amounts.FROM ordersreads every placed order. There is noWHERE; the finance summary covers all months in the data.GROUP BY date_trunc('month', ordered_at)repeats the same truncation expression as the grouping key. Every order placed in March 2025 truncates to2025-03-01and lands in the same group, which is what makes the aggregate produce per-month totals.
The trap
The GROUP BY clause has to repeat the full date_trunc('month', ordered_at) expression. Writing GROUP BY month to reference the SELECT alias fails in PostgreSQL because aliases defined in SELECT are not visible at the GROUP BY evaluation stage. The fix is either to repeat the expression as shown, or to write GROUP BY 1 and reference the first SELECT column by position.
You practiced reducing a per-row timestamp to its month-start with date_trunc so all orders sharing a month collapse into a single row.