Skip to content
PHP2Shell: The PHP Bug
Akshay Thakare

Join Fan-Out: How a Correct Query Returns Wrong Revenue

Joining two child tables of the same parent multiplies rows. Learn to state the grain of every table and aggregate before you join.

SQLIntermediate3 min readUpdated 11 Oct 2026

The bug

An order has one line worth ₹300 and two payment attempts (one failed, one succeeded).

SQL.sql
SELECT SUM(l.line_revenue) AS revenue
FROM order_lines l
JOIN payments p ON p.order_id = l.order_id;
-- returns ₹600 — the single line appears once per payment attempt

Nothing errors. The query is syntactically and relationally "correct". The answer is wrong.

join fan-out
order_lines — one row per line
O101line 1₹300
payments — one row per attempt
O101P101FAILED
O101P102SUCCESS
lines JOIN payments ON order_id
line 1P101₹300
line 1P102₹300
SUM(line_revenue)₹0

Wrong: the single ₹300 line is counted once per payment attempt.

Fix: aggregate payments to one row per order first

line 1 · paid ✓ · ₹300 → SUM = ₹300

One order line, two payment attempts: a direct join doubles the revenue. Aggregating payments to order grain first keeps one row per line.

Why it happens

order_lines and payments are both children of orders. Joining two one-to-many children on the parent key produces a many-to-many result: every line is paired with every payment of the same order.

Entity relationship diagramSwipe to explore →

Rendering entity relationship diagram

The fix: state the grain, then aggregate to it

SQL.sql
WITH paid_orders AS (
  SELECT order_id
  FROM payments
  GROUP BY order_id
  HAVING MAX(CASE WHEN status = 'SUCCESS' THEN 1 ELSE 0 END) = 1   -- one row per order
)
SELECT SUM(l.line_revenue) AS revenue
FROM order_lines l
JOIN paid_orders po ON po.order_id = l.order_id;

Now the right side has exactly one row per order, so lines are not duplicated.

A habit that prevents it

Before writing a join, write one sentence per table: "one row per order line", "one row per payment attempt". If the result of your join has no single-sentence grain, you are about to fan out.

Finished this note?

Share