Why Your AI's Conversion Rate Query Dies on One Empty Group
The percentage query worked until one plan had zero trials. Then Postgres or SQL Server errored, or MySQL returned NULL. Division by COUNT when a group is empty is not a rounding bug — the denominator is zero.
You ask for conversion rate by plan. It works until Launch has zero trials this month. Postgres returns division by zero. MySQL hands you a NULL rate for that row and looks fine otherwise. The SQL is the same query that worked last week.
The denominator is a count that can be zero
SELECT
plan,
COUNT(*) FILTER (WHERE converted) * 100.0 / COUNT(*) AS rate
FROM trials
GROUP BY plan
COUNT(*) is 0 for a plan with no rows in the filtered set — or you divided conversions by COUNT(*) FILTER (WHERE started) and nobody started. That is not integer truncation (3/10 = 0). The divisor is literally zero.
- Postgres / SQL Server — usually an error. The whole query dies, not just that group.
- MySQL — often
NULLfor that cell. Easy to miss in a table of other rates.
Same family as SUM over zero rows → NULL: empty data does not become a friendly zero unless you say so.
The guard
SELECT
plan,
COALESCE(
COUNT(*) FILTER (WHERE converted) * 100.0
/ NULLIF(COUNT(*), 0),
0
) AS rate
FROM trials
GROUP BY plan
NULLIF(count, 0) turns a zero denominator into NULL so the division does not explode. COALESCE(..., 0) is optional — only if you want empty groups to display as 0% instead of blank.
Or drop empty groups:
HAVING COUNT(*) > 0
That's the right move when a rate with no trials should not appear at all — pair with HAVING vs WHERE.
How to catch it
If a report that ran yesterday now errors, ask: "which group has COUNT = 0?" A new plan, a new region, or a weekend with no signups is the usual answer. Don't "fix" it by removing GROUP BY.
The prompt that heads this off
When dividing (rates, averages built by hand, per-user ratios), wrap
the denominator in NULLIF(..., 0). If a group can have zero rows, say
whether you want 0%, NULL, or the row omitted (HAVING COUNT(*) > 0).
Don't let one empty plan fail the whole query.
The short version
A conversion rate needs a denominator. One empty group is enough to crash Postgres — or silently blank a MySQL cell. Guard the divide; don't assume every plan had traffic.
Read-only MCP gateway: mcpserver.design.
Related reading: