Brightlane's pricing report shows each product's price alongside both the catalog-wide average price and the product's own-category average price for ratio analysis.
Write a query to return the ID, category ID, and price of every product, plus the catalog-wide average price and the category average price on each row.
Assumptions:
- The
productstable has one row per product with anid, acategory_id, and aprice. - The catalog-wide average price is the average of
priceacross every product in the table. The same value should appear on every output row. - A category's average price is the average of
priceacross every product in thatcategory_id. The same value should appear on every row that shares acategory_id.
Output:
- One row per product, with columns
id,category_id,price,overall_avg_price, andcategory_avg_price.
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
id,
category_id,
price,
AVG(price) OVER () AS overall_avg_price,
AVG(price) OVER (
PARTITION BY
category_id
) AS category_avg_price
FROM
products The shape
Two window functions live in the same SELECT list with two different windows. AVG(price) OVER () computes the catalog-wide average once and replicates that single value onto every row. AVG(price) OVER (PARTITION BY category_id) computes a per-category average and replicates each category's value onto its own rows. Every product keeps its individual price and gets both averages alongside it.
Clause by clause
SELECT id, category_id, pricereturns each product's identifier, category, and individual price.- The two window columns are:
AVG(price) OVER () AS overall_avg_price,
AVG(price) OVER (PARTITION BY category_id) AS category_avg_priceThe first window, OVER (), is empty: AVG runs across every row in the result, so overall_avg_price is the catalog-wide average, identical on every row. The second window, OVER (PARTITION BY category_id), splits the row set by category: AVG runs inside each category independently, so category_avg_price varies between categories but is the same for every row that shares a category_id.
FROM productsreads the catalog. Both averages are computed over the same source rows; the windows differ only in how they group those rows.
Why two separate windows in one query
The two numbers in the ratio analysis live at different scales: one is global, one is per-category. A single window can only define one scope at a time. To return both alongside each product, each scope needs its own OVER clause, and each OVER clause produces its own column. The fact that both functions are AVG(price) is incidental. What differs is the window definition.
The trap
Reading AVG(price) OVER () and AVG(price) OVER (PARTITION BY category_id) as variations of the same calculation is the easy mistake. They are not variations. They are two independent window functions that happen to share the same input column and the same aggregate. Each OVER (...) defines its own window, computes its own values, and produces its own output column. Swapping the order in the SELECT list, or merging them, would change the report. Each window stands on its own.
You practiced two OVER (...) windows in one statement — one partitionless (the global) and one partitioned (the per-category) — each producing a column whose values vary (or don't) with the row's group membership.