skip to content

Why does base_salary + bonus come back NULL for some rows, and how do you fix it?

level: juniorimportance: should knowfreq 68%

answer

  1. Missing input, missing output
  2. One absent operand is enough
  3. There is a first-non-NULL function for this
  4. It is defined as shorthand for CASE
  5. Also bites string concatenation

basics

~10 s

Arithmetic on an absent value is undefined, so any expression with a NULL operand evaluates to NULL — the whole sum vanishes when bonus is missing. Substitute a default first: base_salary + COALESCE(bonus, 0).

solid answer

~40 s

NULL propagates through scalar expressions. Every arithmetic operator returns NULL when any operand is NULL, because SQL cannot add a number it does not have; `base_salary + bonus` is therefore NULL for every row whose bonus is missing, even though `base_salary` is perfectly well known. The standard fix is `COALESCE`, which returns its first non-NULL argument: `base_salary + COALESCE(bonus, 0)` treats a missing bonus as zero and keeps the sum a number. `COALESCE` is defined in the standard as shorthand for a `CASE` expression, takes two or more arguments of comparable type, and itself returns NULL only when *every* argument is NULL. The judgment call is whether substituting zero is honest — sometimes NULL is the correct output, meaning "we cannot compute this", and coercing it to 0 silently manufactures data.

code

sql · 7 lines
sql
-- total_comp is NULL for every employee without a bonus
SELECT employee_id, base_salary + bonus AS total_comp
FROM payroll;

-- Missing bonus counted as zero
SELECT employee_id, base_salary + COALESCE(bonus, 0) AS total_comp
FROM payroll;

go deeper

for a junior

Recall the rule and the tool: any NULL operand makes the whole expression NULL, and COALESCE supplies a default. Be able to write base_salary + COALESCE(bonus, 0) without hesitating.

for a middle

Explain that COALESCE is standard shorthand for CASE, returns the first non-NULL argument, needs type-compatible arguments, and yields NULL only when all of them are NULL. Connect propagation in expressions to UNKNOWN in predicates.

for a senior

Show judgment about where the defaulting belongs and whether it is honest — a COALESCE buried in a shared view removes a downstream consumer's ability to distinguish zero from missing.

for a principal

Own the modelling question: whether absence should be representable at all for a given measure, and what contract reports and downstream systems get when a value genuinely cannot be computed.

## Propagation is the default SQL scalar expressions are **NULL-propagating**: if an input is absent, the result is absent. ```sql SELECT 100 + NULL; -- NULL SELECT 100 * NULL; -- NULL SELECT NULL / 2; -- NULL SELECT ABS(NULL); -- NULL ``` The reasoning is the same one behind three-valued logic. NULL marks a value that is missing, and there is no defensible number to report for "one hundred plus something unknown". Rather than guess, SQL returns the marker again. This is why a payroll query like ```sql SELECT employee_id, base_salary + bonus AS total_comp FROM payroll; ``` returns a `total_comp` of NULL for exactly the employees who have no bonus row — usually the ones a reader is most interested in, and usually rendered in a report as a blank cell or a zero, depending on the client. ## COALESCE `COALESCE(v1, v2, ...)` evaluates its arguments in order and returns the first that is not NULL; if all are NULL, it returns NULL. It takes two or more arguments and they must be of comparable types, since the result has a single declared type derived from them. ```sql SELECT employee_id, base_salary + COALESCE(bonus, 0) AS total_comp FROM payroll; ``` The standard defines `COALESCE` as pure shorthand for a `CASE` expression: ```sql CASE WHEN v1 IS NOT NULL THEN v1 WHEN v2 IS NOT NULL THEN v2 ELSE NULL END ``` Two practical consequences follow from that definition. First, once a non-NULL argument is found the later ones do not need to be evaluated, so putting an expensive expression last is sensible. Second, the substitute value must make sense as the column's type — `COALESCE(bonus, 'none')` mixing a number with a string is a type error, not a clever default. A common mistake is reaching for `COALESCE` where the real requirement is a *filter*. `COALESCE(bonus, 0) > 0` and `bonus > 0` select the same rows; the difference only matters when the value flows into the output or into arithmetic. ## Comparison expressions are propagating too The same rule shows up on the predicate side, where it becomes three-valued logic: `bonus > 0` is UNKNOWN when `bonus` is NULL, and `WHERE` discards the row. Expression propagation and predicate UNKNOWN-ness are one rule wearing two hats — absent input, undetermined output. This matters when the expression is computed *and then* filtered: ```sql -- rows with a NULL bonus are filtered out here... WHERE base_salary + bonus > 100000 -- ...but retained here, with the missing bonus counted as zero WHERE base_salary + COALESCE(bonus, 0) > 100000 ``` ## String concatenation In standard SQL, `||` propagates like arithmetic does: `'Ms. ' || NULL` is NULL, so building a display name from nullable parts can wipe out the whole string. ```sql -- NULL for anyone without a middle name SELECT first_name || ' ' || middle_name || ' ' || last_name FROM people; -- Always produces a string SELECT first_name || COALESCE(' ' || middle_name, '') || ' ' || last_name FROM people; ``` Note the nesting in the fixed version: the separator space is inside the `COALESCE` argument, so a missing middle name removes its space too rather than leaving a double space. Engines differ here — Oracle treats a NULL operand of `||` as an empty string, and various engines offer concatenation *functions* with their own NULL rules — so if a query must run on more than one engine, be explicit with `COALESCE` rather than relying on the engine's default. ## Is zero the right answer? The technical fix is easy; the judgment is whether the substitution is truthful. Replacing a missing bonus with 0 asserts that the employee received no bonus. If NULL actually means "payroll has not loaded bonuses yet", that assertion is false, and a NULL total is the more honest output — it signals "cannot compute" rather than fabricating a smaller number. Good candidates say this out loud: decide what the NULL means in the domain first, then choose between defaulting it, filtering the row out, or letting the NULL travel to the consumer. A related discipline is where the defaulting lives. Pushing `COALESCE` into a view or a shared query means every downstream consumer inherits the assumption, which is convenient until one consumer needs to tell "zero" from "missing" and can no longer do so. ## Interview framing State the rule (any NULL operand makes the whole scalar expression NULL), give the fix (`COALESCE` with a type-compatible default), and add the caveat that the default is a modelling decision, not just a syntax fix.

  • What does COALESCE return when every argument is NULL?
    NULL. `COALESCE` returns the first argument that is not NULL and falls through to NULL only when none qualifies, which mirrors its definition as a `CASE` expression with an implicit `ELSE NULL`. That is why a final literal default — `COALESCE(a, b, 0)` — is what actually guarantees a non-NULL result.
  • How does NULL behave in string concatenation?
    In standard SQL the `||` operator propagates NULL just as arithmetic does, so `'Ms. ' || NULL` is NULL and one missing name part can blank out an entire display string. Oracle is the notable exception, treating a NULL operand as an empty string. For portable code, wrap the optional fragment in `COALESCE(' ' || middle_name, '')` rather than relying on engine behaviour.
  • When is COALESCE(col, 0) the wrong fix?
    When NULL does not mean zero. If a missing bonus means "not yet loaded" rather than "none awarded", defaulting to zero fabricates a value and hides a data-quality problem; a NULL total honestly reports that the figure cannot be computed. Decide what the absence means in the domain before substituting anything.

saying these in an interview costs you the question

  • Assuming NULL behaves as zero in arithmetic
  • Expecting the engine to skip NULL operands in a sum of columns
  • Thinking COALESCE always returns something non-NULL
  • Mixing incompatible types in COALESCE arguments
  • Defaulting NULLs to zero without asking what the absence means

context