Top 3 Products by Revenue (Jan 2023)
Write a PostgreSQL query to find the top 3 products by total revenue generated from 'completed' orders placed in January 2023. Display the product ID, product name, and their total revenue, rounded to two decimal places, sorted by revenue in descending order (then by product name alphabetically for ties).
Related Concepts
Hint
You'll need to join the three tables to bring together product information, order details (date, status), and sales quantities.
- Filter orders for January 2023 and
'completed'status. - Join
OrderswithOrder_Itemsonorder_id. - Join the result with
Productsonproduct_id. - Calculate revenue for each item:
Products.price * Order_Items.quantity. - Group by
product_idandproduct_nameto sum the revenue per product. - Order the results by total revenue in descending order.
- Use
LIMIT 3to get the top 3 products.
Solution (PostgreSQL)
Imagine you're a detective for Amazon sales: You want to find the 3 "superstar" products that made the most money in January 2023.
- First, you only look at sales receipts (
Orders) from January 2023 that were actually finalized ('completed'status). - Then, for each completed sale, you look at what items were sold (
Order_Items) and how many of each (quantity). - You find the price of each item from the product catalog (
Productstable). - For each item sold, you calculate its mini-revenue:
price × quantity. - Next, you group all these mini-revenues by product. So, if "SuperWidget Pro" was sold 10 times in different orders, you add up all its mini-revenues to get its total January revenue.
- Finally, you list all products by their total revenue, from highest to lowest, and pick the top 3.
PostgreSQL Query
-- Find the top 3 products by total revenue in January 2023
SELECT
p.product_id,
p.product_name,
ROUND(SUM(p.price * oi.quantity), 2) AS total_revenue
FROM
Products p
JOIN
Order_Items oi ON p.product_id = oi.product_id
JOIN
Orders o ON oi.order_id = o.order_id
WHERE
o.status = 'completed'
AND o.order_date >= '2023-01-01'
AND o.order_date <= '2023-01-31'
GROUP BY
p.product_id,
p.product_name
ORDER BY
total_revenue DESC,
p.product_name ASC
LIMIT 3;
Explanation of the PostgreSQL Query:
FROM Products p: Starts with theProductstable, aliased asp.JOIN Order_Items oi ON p.product_id = oi.product_id: Connects each product to its occurrences in order line items.JOIN Orders o ON oi.order_id = o.order_id: Connects the order line items to their parent orders to access order status and date.
WHERE clause):
o.status = 'completed': Ensures only sales that were successfully processed are considered.AND o.order_date >= '2023-01-01' AND o.order_date <= '2023-01-31': Filters orders to include only those placed within January 2023.- An alternative PostgreSQL-specific way to filter for the month is
AND DATE_TRUNC('month', o.order_date) = '2023-01-01'::DATE.
- An alternative PostgreSQL-specific way to filter for the month is
SELECT p.product_id, p.product_name, ...: Selects the product identifiers.ROUND(SUM(p.price * oi.quantity), 2) AS total_revenue:p.price * oi.quantity: Calculates the revenue for each individual line item.SUM(...): Aggregates these line item revenues for each product.ROUND(..., 2): Rounds the total revenue for each product to two decimal places.
GROUP BY p.product_id, p.product_name: This is essential for theSUM()aggregate function. It groups all rows with the same product ID and name together, soSUM()calculates the total revenue per unique product.
ORDER BY total_revenue DESC: Sorts the grouped products by their calculatedtotal_revenuein descending order (highest revenue first)., p.product_name ASC: If two products have the exact same total revenue (a tie), this secondary sort orders them alphabetically by product name.LIMIT 3: Restricts the output to only the top 3 products after sorting.
Using Products.price vs. Order_Items.item_price: The problem uses Products.price as per the instruction. If historical pricing at the moment of sale was required, oi.item_price would be used. It's always good to clarify pricing sources in real-world scenarios.
Your Turn! What are your thoughts on this problem or alternative ways to solve it? Share your own SQL query attempts or insights in the comments below!