Retail · SQL
Reconciling a NGN 18.4M revenue gap by order status
Independent grading: 100/100(verify, opens in a new tab)
Problem
Nura Ibrahim at Meridian Retail (simulated retailer) was investigating a NGN 18.4M discrepancy between gross and net revenue for October 2023. The specific question was: which order statuses account for the gap, and what are the gross and net totals in a single, readable output?
What I did
To answer both questions in one result set, I used GROUP BY ROLLUP(status), which generates a grand-total row automatically alongside the per-status rows. The net revenue column uses a FILTER (WHERE status NOT IN ('cancelled','returned','refunded')) clause on the same SUM, so both figures come from a single table scan. Storing amounts in cents required dividing by 100.0 rather than 100 to avoid integer truncation before any rounding. Ordering with status NULLS LAST places the rollup grand-total row at the bottom, keeping the output easy to read top to bottom.
Finding
Gross revenue for October 2023 was NGN 56,876,792.48 and net revenue was NGN 38,450,382.35, with the NGN 18,426,410.13 gap accounted for entirely by cancelled (NGN 8,854,224.99), returned (NGN 6,000,720.07), and refunded (NGN 3,571,465.07) orders.
Outcome
Nura received a single query result showing per-status revenue and an automatic grand total, giving her a fully documented breakdown of the gap she could share with the broader team without further manipulation.
SELECT
status,
SUM(total_cents) / 100.0 AS gross_revenue,
SUM(total_cents) FILTER (WHERE status NOT IN('cancelled','returned','refunded')) / 100.0 AS net_revenue
FROM retail.orders
WHERE created_at >= '2023-10-01'
AND created_at < '2023-11-01'
GROUP BY ROLLUP(status)
ORDER BY status NULLS LAST;ROLLUP(status) adds a null-status grand-total row automatically, and NULLS LAST pushes it to the bottom so the per-status rows read cleanly before the summary.
Next step
A useful follow-up would be breaking the cancelled orders down by product category to see whether cancellations are concentrated in specific inventory segments rather than spread evenly.
- SUM with FILTER clause for conditional aggregation
- GROUP BY ROLLUP for automatic grand-total rows
- Safe date boundary patterns with >= and < on timestamp columns
- cents-to-currency conversion with floating-point division
- ORDER BY NULLS LAST for controlled sort of rollup rows