PostgreSQL NULLIF returns null when two comparable values are equal; otherwise, it returns the first value. This small expression is particularly useful for ratios whose denominator can be zero. It lets a query represent an undefined result without turning an ordinary reporting condition into a division error.
The important decision is not merely how to stop the error. It is what the result should mean to readers and downstream systems. Zero, unknown, not applicable, and mathematically undefined are different states. Replacing all of them with a convenient zero can make a dashboard look complete while concealing a data problem.
Start with the reporting contract
Suppose a report calculates successful requests divided by total requests. With positive totals, the ratio is straightforward. With no requests, there is no observed success rate. Returning zero implies that requests happened and none succeeded, which is a different claim.
Define that empty-group behavior before writing SQL. A null result can communicate that a ratio is unavailable, provided the application displays it clearly. A separate status field can distinguish no activity from missing source data. Some business rules genuinely define an empty ratio as zero, but that should be an explicit convention rather than an accidental default.
Also define whether negative denominators are possible and valid. Protecting against zero does not validate counts, prevent inconsistent totals, or establish that numerator and denominator describe the same population.
PostgreSQL NULLIF makes zero a nullable denominator
This self-contained example shows three cases without modifying any table:
WITH sample(successes, attempts) AS (
VALUES (8, 10), (0, 0), (NULL, 4)
)
SELECT successes,
attempts,
successes::numeric / NULLIF(attempts, 0) AS success_ratio
FROM sample;
The first row produces a fractional ratio. The second produces null because the zero denominator becomes null. The third also produces null because the numerator is missing. The last two results have the same SQL value but different reasons, so a report may need accompanying counts or status labels.
The numeric cast is deliberate. With integer operands, division can truncate a fractional result. NULLIF prevents a zero-denominator failure, but it does not automatically choose the precision or scale appropriate for your metric.
Understand equality, nulls, and type selection
NULLIF compares its arguments using the applicable equality operator. They must therefore be comparable. A null first argument remains null; it does not turn into the second argument. If the comparison is not true, the first value is returned.
Type resolution can be subtle. The documentation explains that the first value may be promoted to support the chosen equality operator. For example, comparing an integer with a numeric value can yield a numeric result. Do not assume the source column’s type always dictates the final expression’s type unchanged.
Use explicit casts where the output contract requires predictable arithmetic. Test the result metadata through the database driver as well as the values shown in an interactive SQL client. A driver may deliver numeric values differently from floating-point values, and application serialization should preserve the intended meaning.
Use COALESCE only when the default is justified
A common pattern wraps the ratio in COALESCE to display zero. That converts several nullable conditions into one value. It can be useful, but it also destroys information unless the surrounding report carries the reason separately.
Consider an operations dashboard where no traffic should appear as “No observations,” while missing telemetry should raise an alert. A blanket zero default makes both conditions indistinguishable from a real zero-percent success rate. Keep the raw nullable ratio and derive presentation text in a documented layer.
If a downstream interface cannot accept nulls, add a status field or a separate validity indicator. Do not choose a magic numeric sentinel unless every consumer understands it. A negative percentage can be mistaken for an actual measurement in charts or aggregates.
Aggregate the right population first
A ratio of totals is not generally the same as an average of per-row ratios. A day with one request and a day with a thousand requests should not necessarily carry equal weight in an overall success rate. Choose the business interpretation before rearranging the expression for convenience.
For a ratio of aggregates, place NULLIF around the aggregate denominator. Handle the aggregate’s own null behavior explicitly: an empty input, all-null values, and a total of zero may have different meanings. Keep filtering rules aligned so the numerator cannot count events outside the denominator’s scope.
Grouping can also create apparently missing categories. If no row exists for a category, a GROUP BY query may omit that category entirely rather than returning a nullable ratio. When a complete category list is required, build the reporting population deliberately and test the join behavior.
CASE is useful, but not a universal evaluation shield
A CASE expression can express more detailed status rules, such as distinguishing missing data from zero activity. Use it when the meaning deserves several branches instead of trying to encode every state in one denominator expression.
However, SQL evaluation is not identical to procedural line-by-line execution. PostgreSQL documents cases where expressions are evaluated at planning time or another stage. A constant division by zero can fail even inside a CASE branch that would not be chosen at runtime.
Prefer a structurally safe arithmetic expression where possible. Do not rely on an assumed evaluation order in a WHERE clause to protect a later division. Run regression tests against the actual query, including aggregates and parameter types, rather than proving only a simplified fragment.
Test boundaries before publishing a metric
Include positive and zero denominators, null numerators, null denominators, negative values if allowed, and large counts. Test a numerator greater than its denominator to ensure an integrity problem is not silently labeled a valid percentage.
Add groups with no rows and groups containing only nulls. Verify both SQL results and the rendered dashboard. Check exports too: a blank CSV cell, a JSON null, and the string “N/A” are different representations that consumers may handle differently.
Keep rounding at a documented boundary. Rounding every input ratio before aggregation can introduce avoidable distortion. Preserve sufficient precision during calculation, then format the final result for its intended audience.
Frequently asked questions
Does NULLIF change stored data?
Not in a SELECT expression. It changes the expression result. An UPDATE that uses it can change stored values, so review write statements independently.
Can it validate an entire metric?
No. It handles one comparison. Population definition, source completeness, arithmetic type, and business constraints remain separate checks.
Is null always the best result?
Not always. It is often a faithful representation of an unavailable ratio, but the report contract decides. Make any replacement default explicit and test its consequences.
Practical takeaway
Use NULLIF to make problematic denominators nullable without inventing a number. Pair it with explicit numeric types, aligned populations, and clear status presentation. A useful report explains what it knows and what it cannot calculate.
Documentation and related reading
Consult the official documentation for exact behavior in the version you deploy. Continue with the PostgreSQL FILTER aggregate guide for the complementary implementation checks.