Skip to content
codeyourself.
All field guides

One table. Three answers. Conditional aggregation in SQL.

Build a payment summary with CASE, SUM and COALESCE—and verify its meaning with a tiny dataset.

WHAT YOU’LL TAKE AWAY

Choose the output grain before writing SQL.

Treat missing amounts and missing statuses as different cases.

Check aggregation against hand-calculated results.

Start with the grain

Before choosing a function, write the sentence that describes one output row: one row per merchant. Then define each metric. Paid units are the sum of paid amounts. Refunded units are the sum of refunded amounts. Pending count is the number of pending rows. These are three different questions, even though they share one input table.

Use a small independent example. Amounts below are integer units; they are not floating-point currency.

merchantstatusamount_units
Atlaspaid120
AtlaspaidNULL
Atlasrefunded30
Atlaspending80
BirchpendingNULL
Birchpaid45

Atlas should produce 120 paid units, 30 refunded units and one pending row. Birch should produce 45, zero and one. Calculate these results before running your query. A plausible-looking table is not enough to prove that the metric is right.

Express the definitions directly

WITH events(merchant, status, amount_units) AS (
  VALUES ('Atlas', 'paid', 120),
         ('Atlas', 'paid', NULL),
         ('Atlas', 'refunded', 30),
         ('Atlas', 'pending', 80),
         ('Birch', 'pending', NULL),
         ('Birch', 'paid', 45)
)
SELECT merchant,
  SUM(CASE WHEN status = 'paid'
      THEN COALESCE(amount_units, 0) ELSE 0 END) AS paid_units,
  SUM(CASE WHEN status = 'refunded'
      THEN COALESCE(amount_units, 0) ELSE 0 END) AS refunded_units,
  SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count
FROM events
GROUP BY merchant
ORDER BY merchant;

CASE decides each row's contribution. SUM combines those contributions within a merchant. COALESCE makes the chosen missing-amount policy explicit: a paid or refunded row with a missing amount contributes zero. That is a business assumption, not a universal rule. Some reports should reject or separately count missing amounts instead.

Verify the output, then challenge it

merchantpaid_unitsrefunded_unitspending_count
Atlas120301
Birch4501

Add a row with an unknown status. Under this definition it contributes to none of the three metrics. If unknown statuses matter operationally, add an unknown count rather than hiding the issue. Add a merchant with only pending events. It should still appear, with zero paid and refunded units.

A merchant with no input rows will not appear at all. To include every registered merchant, begin with a merchant dimension and LEFT JOIN the events. Be careful: COUNT(*) after that join also counts the placeholder row for a merchant without matching events. Count a non-null event ID or use a carefully defined condition instead.

Common mistakes worth catching early

SUM(amount_units) does not count rows. COUNT(amount_units) excludes NULL amounts. A misspelled column cannot be fixed with COALESCE because the name must resolve before the expression runs. Inspect the schema and use the actual column name. Finally, adding a join can multiply rows: confirm the join cardinality before trusting any sum.

The habit to carry forward is simple: define grain, define contributions, calculate a tiny expected result and test edge cases. The SQL follows those decisions.

PAUSE. THINK. EXPLAIN.

Check your understanding

A merchant has two pending rows: one has amount 12 and one has NULL. Should pending_count be 1, 2 or 12?

Reveal the reasoning

It is 2 when the metric counts pending rows. Amount is irrelevant to that definition. If you mean unique pending orders, use an order identifier and a distinct-order rule instead.

Sources & further reading

Created with AI assistance. Examples are educational; verify platform-specific behavior against the linked documentation. Found something unclear? Contact us.

FROM UNDERSTANDING TO DOING

Make the next step a small one.

Create a Free account for six coding challenges and five starter labs. Advanced labs and Architecture Studio are Pro features.

Start free practice
Save your own workPractice with browser fixturesClear Free & Pro accessSQL practice uses local DuckDB fixtures. System-design simulations model assumptions; they do not provision real cloud services.