Skip to course content
Free SQL course

SQL for Data Analysis and AI

Unit 05.03: Pre-aggregating the many-side as the fix

Unit ID: SQL-M05-U04 - Estimated active time: 15-18 minutes Objective: combine order-level and item-level measures in one result without double counting.

The requirement that tempts the bug

"Show me revenue and item count together." Revenue lives at order grain; item count lives at item grain. Joining them naively inflates revenue, as the previous unit showed.

Fix 1 - do not join at all

If every measure you need is on one table, stay there:

SELECT COUNT(*) AS orders, SUM(order_total) AS revenue
FROM orders;
-- 1000 | 2701463.00

The best fix for a fan-out bug is frequently the join you did not write.

Fix 2 - collapse the many side first, then join

Aggregate order_items to one row per order before joining. The join becomes one-to-one and nothing is duplicated:

SELECT
  SUM(o.order_total) AS revenue,
  SUM(i.item_count)  AS items
FROM orders o
LEFT JOIN (
  SELECT order_id, COUNT(*) AS item_count
  FROM order_items
  GROUP BY order_id
) i ON i.order_id = o.order_id;
-- 2701463.00 | 3400

Both figures are now correct in a single result: ₹27,01,463 revenue and 3,400 items. Compare with the ₹91,47,789 from the naive join - same tables, same intent, one correct answer.

LEFT JOIN rather than JOIN matters here: it keeps orders that have no items instead of silently dropping them.

Fix 3 - the one to be careful with

SUM(DISTINCT order_total) is tempting and usually wrong:

SELECT SUM(DISTINCT o.order_total)
FROM orders o JOIN order_items i ON i.order_id = o.order_id;
-- 2700463.00

Close to the true ₹27,01,463 - and short by exactly ₹1,000. Orders 500 and 501 both total ₹1,000, so DISTINCT treats them as one value and discards a real order's revenue. It deduplicates *values*, not *rows*, and legitimate repeated amounts disappear.

A fix that is nearly right is worse than one that is obviously wrong, because it survives review.

Practice

Produce one result showing, per status: order count, revenue, and total items - all correct.

Check your answer
SELECT
  o.status,
  COUNT(*)                    AS orders,
  SUM(o.order_total)          AS revenue,
  SUM(COALESCE(i.item_count, 0)) AS items
FROM orders o
LEFT JOIN (
  SELECT order_id, COUNT(*) AS item_count
  FROM order_items
  GROUP BY order_id
) i ON i.order_id = o.order_id
GROUP BY o.status
ORDER BY revenue DESC;

Pre-aggregating keeps the join one-to-one, so COUNT(*) counts orders and SUM(o.order_total) is not duplicated. COALESCE guards the case where an order has no items.

Takeaway

Collapse the many side to the grain you need, then join. Reach for DISTINCT last - it removes values, not duplicates, and quietly deletes real data.

---